diff --git a/pack/acp/start/vim-go/.dockerignore b/pack/acp/start/vim-go/.dockerignore
index 286d089..8a4b0bb 100644
--- a/pack/acp/start/vim-go/.dockerignore
+++ b/pack/acp/start/vim-go/.dockerignore
@@ -4,3 +4,5 @@
.dlv/
.git/
.viminfo
+issues/
+autoload/go/**/pkg/
diff --git a/pack/acp/start/vim-go/.github/FUNDING.yml b/pack/acp/start/vim-go/.github/FUNDING.yml
new file mode 100644
index 0000000..060026e
--- /dev/null
+++ b/pack/acp/start/vim-go/.github/FUNDING.yml
@@ -0,0 +1 @@
+patreon: bhcleek
diff --git a/pack/acp/start/vim-go/.github/ISSUE_TEMPLATE.md b/pack/acp/start/vim-go/.github/ISSUE_TEMPLATE.md
index f227239..687b021 100644
--- a/pack/acp/start/vim-go/.github/ISSUE_TEMPLATE.md
+++ b/pack/acp/start/vim-go/.github/ISSUE_TEMPLATE.md
@@ -1,21 +1,47 @@
-### What did you do? (required. The issue will be **closed** when not provided.)
+
+### What did you do? (required: The issue will be **closed** when not provided)
+
+
### What did you expect to happen?
-
### What happened instead?
-
### 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:
+
+
+vimrc
-* Go version (`go version`):
+
-* Go environment (`go env`):
+#### Vim version (first three lines from `:version`):
+
+#### Go version (`go version`):
+
+
+#### Go environment
+go env
Output:
+
+
+
+
+#### gopls version
+gopls version
Output:
+
+
+
diff --git a/pack/acp/start/vim-go/.github/workflows/test.yml b/pack/acp/start/vim-go/.github/workflows/test.yml
new file mode 100644
index 0000000..01b0875
--- /dev/null
+++ b/pack/acp/start/vim-go/.github/workflows/test.yml
@@ -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
diff --git a/pack/acp/start/vim-go/.gitignore b/pack/acp/start/vim-go/.gitignore
index 5c64849..2f7d833 100644
--- a/pack/acp/start/vim-go/.gitignore
+++ b/pack/acp/start/vim-go/.gitignore
@@ -1,5 +1,11 @@
-.DS_Store
-/doc/tags
-/.coverage.covimerage
-/coverage.xml
*.pyc
+.DS_Store
+/.bash_history
+/.cache
+/.config
+/.coverage.covimerage
+/.local
+/.viminfo
+/coverage.xml
+/doc/tags
+/issues
diff --git a/pack/acp/start/vim-go/.travis.yml b/pack/acp/start/vim-go/.travis.yml
deleted file mode 100644
index 9d7abd6..0000000
--- a/pack/acp/start/vim-go/.travis.yml
+++ /dev/null
@@ -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
diff --git a/pack/acp/start/vim-go/CHANGELOG.md b/pack/acp/start/vim-go/CHANGELOG.md
index 09a3a6d..561a18f 100644
--- a/pack/acp/start/vim-go/CHANGELOG.md
+++ b/pack/acp/start/vim-go/CHANGELOG.md
@@ -1,5 +1,529 @@
## 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)
FEATURES:
@@ -13,7 +537,7 @@ FEATURES:
* New `:GoDefType` command to jump to a type definition from an instance of the
type.
-BACKWARDS INCOMPATABILITIES:
+BACKWARDS INCOMPATIBILITIES:
* `g:go_highlight_function_arguments` is renamed to `g:go_highlight_function_parameters`
[[GH-2117]](https://github.com/fatih/vim-go/pull/2117)
diff --git a/pack/acp/start/vim-go/Dockerfile b/pack/acp/start/vim-go/Dockerfile
index 7f03ba7..344d0c8 100644
--- a/pack/acp/start/vim-go/Dockerfile
+++ b/pack/acp/start/vim-go/Dockerfile
@@ -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 clean && \
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
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/
WORKDIR /vim-go
-RUN scripts/install-vim vim-7.4
-RUN scripts/install-vim vim-8.0
-RUN scripts/install-vim nvim
+RUN scripts/install-tools vim-8.0
+RUN scripts/install-tools vim-8.2
+RUN scripts/install-tools nvim
ENTRYPOINT ["make"]
diff --git a/pack/acp/start/vim-go/Makefile b/pack/acp/start/vim-go/Makefile
index 717eb0e..2807e0d 100644
--- a/pack/acp/start/vim-go/Makefile
+++ b/pack/acp/start/vim-go/Makefile
@@ -1,4 +1,4 @@
-VIMS ?= vim-7.4 vim-8.0 nvim
+VIMS ?= vim-8.0 vim-8.2 nvim
all: install lint test
@@ -6,6 +6,7 @@ install:
@echo "==> Installing Vims: $(VIMS)"
@for vim in $(VIMS); do \
./scripts/install-vim $$vim; \
+ ./scripts/install-tools $$vim; \
done
test:
@@ -16,7 +17,7 @@ test:
lint:
@echo "==> Running linting tools"
- @./scripts/lint vim-8.0
+ @./scripts/lint vim-8.2
docker:
@echo "==> Building/starting Docker container"
diff --git a/pack/acp/start/vim-go/README.md b/pack/acp/start/vim-go/README.md
index c47c8f3..157bbea 100644
--- a/pack/acp/start/vim-go/README.md
+++ b/pack/acp/start/vim-go/README.md
@@ -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)
+
+
@@ -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`.
* Improved syntax highlighting and folding.
* Debug programs with integrated `delve` support with `:GoDebugStart`.
-* Completion support via `gocode` and `gopls`.
-* `gofmt` or `goimports` on save keeps the cursor position and undo history.
+* Completion and many other features support via `gopls`.
+* formatting on save keeps the cursor position and undo history.
* Go to symbol/declaration with `:GoDef`.
* Look up documentation with `:GoDoc` or `:GoDocBrowser`.
* Easily import packages via `:GoImport`, remove them via `:GoDrop`.
* Precise type-safe renaming of identifiers with `:GoRename`.
* See which code is covered by tests with `:GoCoverage`.
* 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
quickfix or location list.
* 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`.
* ... and many more! Please see [doc/vim-go.txt](doc/vim-go.txt) for more
information.
+* The `gopls` instance can be shared with other Vim plugins.
+* Vim-go's use of `gopls` can be disabled.
## 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
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)
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
The BSD 3-Clause License - see [`LICENSE`](LICENSE) for more details
+
diff --git a/pack/acp/start/vim-go/autoload/fzf/decls.vim b/pack/acp/start/vim-go/autoload/fzf/decls.vim
index 20e4f04..02bf462 100644
--- a/pack/acp/start/vim-go/autoload/fzf/decls.vim
+++ b/pack/acp/start/vim-go/autoload/fzf/decls.vim
@@ -121,10 +121,10 @@ function! s:source(mode,...) abort
\ decl.col
\)
call add(ret_decls, printf("%s\t%s %s\t%s",
- \ s:color(decl.ident . space, "Function"),
- \ s:color(decl.keyword, "Keyword"),
- \ s:color(pos, "SpecialComment"),
- \ s:color(decl.full, "Comment"),
+ \ s:color(decl.ident . space, "goDeclsFzfFunction"),
+ \ s:color(decl.keyword, "goDeclsFzfKeyword"),
+ \ s:color(pos, "goDeclsFzfSpecialComment"),
+ \ s:color(decl.full, "goDeclsFzfComment"),
\))
endfor
diff --git a/pack/acp/start/vim-go/autoload/go/auto.vim b/pack/acp/start/vim-go/autoload/go/auto.vim
index 1174858..4884320 100644
--- a/pack/acp/start/vim-go/autoload/go/auto.vim
+++ b/pack/acp/start/vim-go/autoload/go/auto.vim
@@ -11,7 +11,43 @@ function! go#auto#template_autocreate()
call go#template#create()
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()
return
endif
@@ -21,46 +57,109 @@ function! go#auto#echo_go_info()
endif
let item = v:completed_item
- if !has_key(item, "info")
+ if !has_key(item, "user_data")
return
endif
- if empty(item.info)
+ if empty(item.user_data)
return
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
-function! go#auto#auto_type_info()
- if !go#config#AutoTypeInfo() || !filereadable(expand('%:p'))
+let s:timer_id = 0
+
+" 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
endif
- " GoInfo automatic update
- call go#tool#Info(0)
+ if has_timer
+ augroup vim-go-buffer-auto
+ autocmd! *
+ augroup END
+ let b:has_timer = 0
+ call s:timer_stop()
+ return
+ endif
+
+ augroup vim-go-buffer-auto
+ autocmd! *
+ autocmd CursorMoved call s:timer_restart()
+ autocmd BufLeave call s:timer_stop()
+ augroup END
+ let b:has_timer = 1
+ call s:timer_start()
endfunction
-function! go#auto#auto_sameids()
- if !go#config#AutoSameids() || !filereadable(expand('%:p'))
- return
+function! s:timer_restart()
+ if isdirectory(expand('%:p:h'))
+ call s:timer_stop()
+ call s:timer_start()
endif
+endfunction
- " GoSameId automatic update
- call go#guru#SameIds(0)
+function! s:timer_stop()
+ 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
function! go#auto#fmt_autosave()
- if !go#config#FmtAutosave() || !filereadable(expand('%:p'))
+ if !(isdirectory(expand('%:p:h')) && expand(':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
endif
- " Go code formatting on save
call go#fmt#Format(-1)
endfunction
function! go#auto#metalinter_autosave()
- if !go#config#MetalinterAutosave() || !filereadable(expand('%:p'))
+ if !go#config#MetalinterAutosave() || !isdirectory(expand('%:p:h'))
return
endif
@@ -69,7 +168,7 @@ function! go#auto#metalinter_autosave()
endfunction
function! go#auto#modfmt_autosave()
- if !go#config#ModFmtAutosave() || !filereadable(expand('%:p'))
+ if !(go#config#ModFmtAutosave() && isdirectory(expand('%:p:h')) && expand(':p') == expand('%:p'))
return
endif
@@ -78,7 +177,7 @@ function! go#auto#modfmt_autosave()
endfunction
function! go#auto#asmfmt_autosave()
- if !go#config#AsmfmtAutosave() || !filereadable(expand('%:p'))
+ if !(go#config#AsmfmtAutosave() && isdirectory(expand('%:p:h')) && expand(':p') == expand('%:p'))
return
endif
diff --git a/pack/acp/start/vim-go/autoload/go/cmd.vim b/pack/acp/start/vim-go/autoload/go/cmd.vim
index fdf2299..8867166 100644
--- a/pack/acp/start/vim-go/autoload/go/cmd.vim
+++ b/pack/acp/start/vim-go/autoload/go/cmd.vim
@@ -9,8 +9,8 @@ function! go#cmd#autowrite() abort
for l:nr in range(0, bufnr('$'))
if buflisted(l:nr) && getbufvar(l:nr, '&modified')
" Sleep one second to make sure people see the message. Otherwise it is
- " often immediacy overwritten by the async messages (which also don't
- " invoke the "hit ENTER" prompt).
+ " often immediately overwritten by the async messages (which also
+ " doesn't invoke the "hit ENTER" prompt).
call go#util#EchoWarning('[No write since last change]')
sleep 1
return
@@ -32,7 +32,7 @@ function! go#cmd#Build(bang, ...) abort
\ map(copy(a:000), "expand(v:val)") +
\ [".", "errors"]
- " Vim and Neovim async.
+ " Vim and Neovim async
if go#util#has_job()
call s:cmd_job({
\ 'cmd': ['go'] + args,
@@ -41,8 +41,15 @@ function! go#cmd#Build(bang, ...) abort
\ 'statustype': 'build'
\})
- " Vim 7.4 without async
+ " Vim without async
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 &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))
if !empty(errors) && !a:bang
call go#list#JumpToFirst(l:listtype)
+ let l:status.state = 'failed'
else
- call go#util#EchoSuccess("[build] SUCCESS")
+ let l:status.state = 'success'
+ if go#config#EchoCommandInfo()
+ call go#util#EchoSuccess("[build] SUCCESS")
+ endif
endif
+ call go#statusline#Update(expand('%:p:h'), l:status)
endif
+
endfunction
@@ -104,16 +117,15 @@ endfunction
" Run runs the current file (and their dependencies if any) in a new terminal.
function! go#cmd#RunTerm(bang, mode, files) abort
- let cmd = "go run "
- let tags = go#config#BuildTags()
- if len(tags) > 0
- let cmd .= "-tags " . go#util#Shellescape(tags) . " "
+ let cmd = ["go", "run"]
+ if len(go#config#BuildTags()) > 0
+ call extend(cmd, ["-tags", go#config#BuildTags()])
endif
if empty(a:files)
- let cmd .= go#util#Shelljoin(go#tool#Files())
+ call extend(cmd, go#tool#Files())
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
call go#term#newmode(a:bang, cmd, s:runerrorformat(), a:mode)
endfunction
@@ -123,7 +135,7 @@ endfunction
" suitable for long running apps, because vim is blocking by default and
" calling long running apps will block the whole UI.
function! go#cmd#Run(bang, ...) abort
- if has('nvim')
+ if go#config#TermEnabled()
call go#cmd#RunTerm(a:bang, '', a:000)
return
endif
@@ -134,61 +146,101 @@ function! go#cmd#Run(bang, ...) abort
" anything. Once this is implemented we're going to make :GoRun async
endif
- let cmd = "go run "
- let tags = go#config#BuildTags()
- if len(tags) > 0
- let cmd .= "-tags " . go#util#Shellescape(tags) . " "
+ let l:status = {
+ \ 'desc': 'current status',
+ \ 'type': 'run',
+ \ '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
+ 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 a:0 == 0
- exec '!' . cmd . go#util#Shelljoin(go#tool#Files(), 1)
- else
- exec '!' . cmd . go#util#Shelljoin(map(copy(a:000), "expand(v:val)"), 1)
- endif
+ try
+ if go#util#HasDebug('shell-commands')
+ call go#util#EchoInfo(printf('shell command: %s', string(l:cmd)))
+ 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
- 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
- redraws! | echon "vim-go: [run] " | echohl Function | echon "SUCCESS"| echohl None
+ if go#config#EchoCommandInfo()
+ redraws!
+ call go#util#EchoSuccess('[run] SUCCESS')
+ endif
endif
+ call go#statusline#Update(expand('%:p:h'), l:status)
return
endif
" :make expands '%' and '#' wildcards, so they must also be escaped
- let default_makeprg = &makeprg
- if a:0 == 0
- 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:default_makeprg = &makeprg
+ let &makeprg = go#util#Shelljoin(l:cmd, 1)
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
let l:old_errorformat = &errorformat
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"
exe 'lmake!'
else
exe 'make!'
endif
finally
- "restore errorformat
+ "restore the working directory, errformat, and makeprg
+ execute cd . fnameescape(l:dir)
let &errorformat = l:old_errorformat
- let &makeprg = default_makeprg
+ let &makeprg = l:default_makeprg
endtry
let l:errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(l:errors))
- if !empty(l:errors) && !a:bang
- call go#list#JumpToFirst(l:listtype)
+ if !empty(l:errors)
+ let l:status.state = 'failed'
+ if !a:bang
+ call go#list#JumpToFirst(l:listtype)
+ endif
endif
-
+ call go#statusline#Update(expand('%:p:h'), l:status)
endfunction
" 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
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
if l:listtype == "locationlist"
@@ -273,13 +334,18 @@ function! go#cmd#Generate(bang, ...) abort
let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors))
if !empty(errors)
+ let l:status.status = 'failed'
if !a:bang
call go#list#JumpToFirst(l:listtype)
endif
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
-
+ call go#statusline#Update(expand(':%:p:h'), l:status)
endfunction
function! s:runerrorformat()
@@ -288,6 +354,18 @@ function! s:runerrorformat()
return l:errorformat
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 |
" ---------------------
diff --git a/pack/acp/start/vim-go/autoload/go/complete.vim b/pack/acp/start/vim-go/autoload/go/complete.vim
index 987c484..b256e8c 100644
--- a/pack/acp/start/vim-go/autoload/go/complete.vim
+++ b/pack/acp/start/vim-go/autoload/go/complete.vim
@@ -2,276 +2,48 @@
let s:cpo_save = &cpo
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
" cursor.
function! go#complete#GetInfo() abort
- return s:sync_info(0)
-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("") . '\>'
- " 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
+ return go#lsp#GetInfo()
endfunction
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.done = 1
endfunction
"findstart = 1 when we need to get the start of the match
if a:findstart == 1
- call go#lsp#Completion(expand('%:p'), line('.'), col('.'), funcref('s:handler', [l:state]))
+ 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
sleep 10m
endwhile
- let s:completions = l:state.matches
-
if len(l:state.matches) == 0
" no matches. cancel and leave completion mode.
call go#util#EchoInfo("no matches")
return -3
endif
- return col('.')
+ 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
return s:completions
endif
@@ -281,11 +53,11 @@ function! go#complete#ToggleAutoTypeInfo() abort
if go#config#AutoTypeInfo()
call go#config#SetAutoTypeInfo(0)
call go#util#EchoProgress("auto type info disabled")
- return
- end
-
- call go#config#SetAutoTypeInfo(1)
- call go#util#EchoProgress("auto type info enabled")
+ else
+ call go#config#SetAutoTypeInfo(1)
+ call go#util#EchoProgress("auto type info enabled")
+ endif
+ call go#auto#update_autocmd()
endfunction
" restore Vi compatibility settings
diff --git a/pack/acp/start/vim-go/autoload/go/complete_test.vim b/pack/acp/start/vim-go/autoload/go/complete_test.vim
index 4397142..ee00056 100644
--- a/pack/acp/start/vim-go/autoload/go/complete_test.vim
+++ b/pack/acp/start/vim-go/autoload/go/complete_test.vim
@@ -2,23 +2,24 @@
let s:cpo_save = &cpo
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:tmp = gotest#load_fixture(l:filename)
+ try
+ call cursor(8, 3)
- call cursor(8, 3)
-
- let g:go_info_mode = 'gocode'
- let expected = 'func Example(s string)'
- let actual = go#complete#GetInfo()
- call assert_equal(expected, actual)
-
- let g:go_info_mode = 'guru'
- call go#config#InfoMode()
- let actual = go#complete#GetInfo()
- call assert_equal(expected, actual)
-
- unlet g:go_info_mode
+ let expected = 'func Example(s string)'
+ let actual = go#complete#GetInfo()
+ call assert_equal(expected, actual)
+ finally
+ call delete(l:tmp, 'rf')
+ endtry
endfunction
" restore Vi compatibility settings
diff --git a/pack/acp/start/vim-go/autoload/go/config.vim b/pack/acp/start/vim-go/autoload/go/config.vim
index a3ef0a6..0e4fdeb 100644
--- a/pack/acp/start/vim-go/autoload/go/config.vim
+++ b/pack/acp/start/vim-go/autoload/go/config.vim
@@ -21,10 +21,12 @@ endfunction
function! go#config#SetBuildTags(value) abort
if a:value is ''
silent! unlet g:go_build_tags
+ call go#lsp#ResetWorkspaceDirectories()
return
endif
let g:go_build_tags = a:value
+ call go#lsp#ResetWorkspaceDirectories()
endfunction
function! go#config#TestTimeout() abort
@@ -47,8 +49,23 @@ function! go#config#TermMode() abort
return get(g:, 'go_term_mode', 'vsplit')
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
- 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
function! go#config#SetTermEnabled(value) abort
@@ -72,7 +89,18 @@ function! go#config#StatuslineDuration() abort
endfunction
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
function! go#config#PlayBrowserCommand() abort
@@ -114,7 +142,7 @@ function! go#config#ListAutoclose() abort
endfunction
function! go#config#InfoMode() abort
- return get(g:, 'go_info_mode', 'gocode')
+ return get(g:, 'go_info_mode', 'gopls')
endfunction
function! go#config#GuruScope() abort
@@ -139,30 +167,13 @@ function! go#config#SetGuruScope(scope) abort
endif
endfunction
-function! go#config#GocodeUnimportedPackages() abort
- return get(g:, 'go_gocode_unimported_packages', 0)
-endfunction
-
-let s:sock_type = (has('win32') || has('win64')) ? 'tcp' : 'unix'
-function! go#config#GocodeSocketType() abort
- return get(g:, 'go_gocode_socket_type', s:sock_type)
-endfunction
-
-function! go#config#GocodeProposeBuiltins() abort
- return get(g:, 'go_gocode_propose_builtins', 1)
-endfunction
-
-function! go#config#GocodeProposeSource() abort
- return get(g:, 'go_gocode_propose_source', 0)
-endfunction
-
function! go#config#EchoCommandInfo() abort
return get(g:, 'go_echo_command_info', 1)
endfunction
function! go#config#DocUrl() abort
- let godoc_url = get(g:, 'go_doc_url', 'https://godoc.org')
- if godoc_url isnot 'https://godoc.org'
+ let godoc_url = get(g:, 'go_doc_url', 'https://pkg.go.dev')
+ if godoc_url isnot 'https://pkg.go.dev'
" strip last '/' character if available
let last_char = strlen(godoc_url) - 1
if godoc_url[last_char] == '/'
@@ -174,12 +185,15 @@ function! go#config#DocUrl() abort
return godoc_url
endfunction
+function! go#config#DocPopupWindow() abort
+ return get(g:, 'go_doc_popup_window', 0)
+endfunction
function! go#config#DefReuseBuffer() abort
return get(g:, 'go_def_reuse_buffer', 0)
endfunction
function! go#config#DefMode() abort
- return get(g:, 'go_def_mode', 'guru')
+ return get(g:, 'go_def_mode', 'gopls')
endfunction
function! go#config#DeclsIncludes() abort
@@ -192,9 +206,10 @@ endfunction
function! go#config#DebugWindows() abort
return get(g:, 'go_debug_windows', {
- \ 'stack': 'leftabove 20vnew',
- \ 'out': 'botright 10new',
\ 'vars': 'leftabove 30vnew',
+ \ 'stack': 'leftabove 20new',
+ \ 'goroutines': 'botright 10new',
+ \ 'out': 'botright 5new',
\ }
\ )
@@ -211,7 +226,7 @@ function! go#config#DebugCommands() abort
endfunction
function! go#config#DebugLogOutput() abort
- return get(g:, 'go_debug_log_output', 'debugger, rpc')
+ return get(g:, 'go_debug_log_output', 'debugger,rpc')
endfunction
function! go#config#LspLog() abort
@@ -236,6 +251,10 @@ function! go#config#AddtagsTransform() abort
return get(g:, 'go_addtags_transform', "snakecase")
endfunction
+function! go#config#AddtagsSkipUnexported() abort
+ return get(g:, 'go_addtags_skip_unexported', 0)
+endfunction
+
function! go#config#TemplateAutocreate() abort
return get(g:, "go_template_autocreate", 1)
endfunction
@@ -245,31 +264,15 @@ function! go#config#SetTemplateAutocreate(value) abort
endfunction
function! go#config#MetalinterCommand() abort
- return get(g:, "go_metalinter_command", "gometalinter")
+ return get(g:, "go_metalinter_command", "golangci-lint")
endfunction
function! go#config#MetalinterAutosaveEnabled() abort
- let l:default_enabled = ["vet", "golint"]
-
- if go#config#MetalinterCommand() == "golangci-lint"
- let l:default_enabled = ["govet", "golint"]
- endif
-
- return get(g:, "go_metalinter_autosave_enabled", default_enabled)
+ return get(g:, "go_metalinter_autosave_enabled", ["govet", "golint"])
endfunction
function! go#config#MetalinterEnabled() abort
- let l:default_enabled = ["vet", "golint", "errcheck"]
-
- if go#config#MetalinterCommand() == "golangci-lint"
- let l:default_enabled = ["govet", "golint"]
- endif
-
- return get(g:, "go_metalinter_enabled", default_enabled)
-endfunction
-
-function! go#config#MetalinterDisabled() abort
- return get(g:, "go_metalinter_disabled", [])
+ return get(g:, "go_metalinter_enabled", ["vet", "golint", "errcheck"])
endfunction
function! go#config#GolintBin() abort
@@ -296,6 +299,10 @@ function! go#config#FmtAutosave() abort
return get(g:, "go_fmt_autosave", 1)
endfunction
+function! go#config#ImportsAutosave() abort
+ return get(g:, 'go_imports_autosave', 0)
+endfunction
+
function! go#config#SetFmtAutosave(value) abort
let g:go_fmt_autosave = a:value
endfunction
@@ -340,8 +347,12 @@ function! go#config#FmtCommand() abort
return get(g:, "go_fmt_command", "gofmt")
endfunction
+function! go#config#ImportsMode() abort
+ return get(g:, "go_imports_mode", "goimports")
+endfunction
+
function! go#config#FmtOptions() abort
- return get(g:, "go_fmt_options", {})
+ return get(b:, "go_fmt_options", get(g:, "go_fmt_options", {}))
endfunction
function! go#config#FmtFailSilently() abort
@@ -356,8 +367,13 @@ function! go#config#PlayOpenBrowser() abort
return get(g:, "go_play_open_browser", 1)
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
- return get(g:, "go_gorename_bin", "gorename")
+ return get(g:, "go_gorename_bin", "gopls")
endfunction
function! go#config#GorenamePrefill() abort
@@ -451,10 +467,22 @@ function! go#config#HighlightVariableDeclarations() abort
return get(g:, 'go_highlight_variable_declarations', 0)
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
return get(g:, 'go_highlight_debug', 1)
endfunction
+function! go#config#DebugBreakpointSignText() abort
+ return get(g:, 'go_debug_breakpoint_sign_text', '>')
+endfunction
+
function! go#config#FoldEnable(...) abort
if a:0 > 0
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)
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
" compatibility reasons.
if exists("g:go_gorename_prefill") && g:go_gorename_prefill == 1
diff --git a/pack/acp/start/vim-go/autoload/go/config_test.vim b/pack/acp/start/vim-go/autoload/go/config_test.vim
new file mode 100644
index 0000000..6ac3eed
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/config_test.vim
@@ -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
diff --git a/pack/acp/start/vim-go/autoload/go/coverage.vim b/pack/acp/start/vim-go/autoload/go/coverage.vim
index 50d4df1..01456e5 100644
--- a/pack/acp/start/vim-go/autoload/go/coverage.vim
+++ b/pack/acp/start/vim-go/autoload/go/coverage.vim
@@ -25,10 +25,10 @@ endfunction
" the code. Calling it again reruns the tests and shows the last updated
" coverage.
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")
- 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
endif
diff --git a/pack/acp/start/vim-go/autoload/go/debug.vim b/pack/acp/start/vim-go/autoload/go/debug.vim
index 1d11301..abe7144 100644
--- a/pack/acp/start/vim-go/autoload/go/debug.vim
+++ b/pack/acp/start/vim-go/autoload/go/debug.vim
@@ -6,14 +6,15 @@ scriptencoding utf-8
if !exists('s:state')
let s:state = {
- \ 'rpcid': 1,
- \ 'running': 0,
- \ 'currentThread': {},
- \ 'localVars': {},
- \ 'functionArgs': {},
- \ 'message': [],
- \ 'is_test': 0,
- \}
+ \ 'rpcid': 1,
+ \ 'running': 0,
+ \ 'currentThread': {},
+ \ 'localVars': {},
+ \ 'functionArgs': {},
+ \ 'message': [],
+ \ 'resultHandlers': {},
+ \ 'kill_on_detach': v:true,
+ \ }
if go#util#HasDebug('debugger-state')
call go#config#SetDebugDiag(s:state)
@@ -24,7 +25,7 @@ if !exists('s:start_args')
let s:start_args = []
endif
-function! s:groutineID() abort
+function! s:goroutineID() abort
return s:state['currentThread'].goroutineID
endfunction
@@ -81,15 +82,23 @@ function! s:logger(prefix, ch, msg) abort
endtry
endfunction
-function! s:call_jsonrpc(method, ...) abort
+" s:call_jsonrpc will call method, passing all of s:call_jsonrpc's optional
+" arguments in the rpc request's params field.
+
+" The first argument to s:call_jsonrpc should be a function that takes two
+" arguments. The first argument will be a function that takes no arguments and will
+" throw an exception if the response to the request is an error response. The
+" second argument is the response itself.
+function! s:call_jsonrpc(handle_result, method, ...) abort
if go#util#HasDebug('debugger-commands')
- echom 'sending to dlv ' . a:method
+ call go#util#EchoInfo('sending to dlv ' . a:method)
endif
let l:args = a:000
let s:state['rpcid'] += 1
+ let l:reqid = s:state['rpcid']
let l:req_json = json_encode({
- \ 'id': s:state['rpcid'],
+ \ 'id': l:reqid,
\ 'method': a:method,
\ 'params': l:args,
\})
@@ -98,36 +107,33 @@ function! s:call_jsonrpc(method, ...) abort
let l:ch = s:state['ch']
if has('nvim')
call chansend(l:ch, l:req_json)
- while len(s:state.data) == 0
- sleep 50m
- if get(s:state, 'ready', 0) == 0
- return
- endif
- endwhile
- let resp_json = s:state.data[0]
- let s:state.data = s:state.data[1:]
else
call ch_sendraw(l:ch, req_json)
- let l:resp_raw = ch_readraw(l:ch)
- let resp_json = json_decode(l:resp_raw)
endif
+ let s:state.resultHandlers[l:reqid] = a:handle_result
+
if go#util#HasDebug('debugger-commands')
let g:go_debug_commands = add(go#config#DebugCommands(), {
\ 'request': l:req_json,
- \ 'response': l:resp_json,
\ })
endif
- if type(l:resp_json) == v:t_dict && has_key(l:resp_json, 'error') && !empty(l:resp_json.error)
- throw l:resp_json.error
- endif
- return l:resp_json
+ redraw
catch
throw substitute(v:exception, '^Vim', '', '')
endtry
endfunction
+function! s:exited(res) abort
+ if type(a:res) ==# type(v:null)
+ return 0
+ endif
+
+ let state = a:res.result.State
+ return state.exited == v:true
+endfunction
+
" Update the location of the current breakpoint or line we're halted on based on
" response from dlv.
function! s:update_breakpoint(res) abort
@@ -155,13 +161,23 @@ function! s:update_breakpoint(res) abort
endif
silent! exe 'norm!' linenr.'G'
silent! normal! zvzz
+ " TODO(bc): convert to use s:sign_unplace()
silent! sign unplace 9999
+ " TODO(bc): convert to use s:sign_place()
silent! exe 'sign place 9999 line=' . linenr . ' name=godebugcurline file=' . filename
+ call s:warn_when_stale(fnamemodify(l:filename, ':p'))
endfunction
" Populate the stacktrace window.
-function! s:show_stacktrace(res) abort
- if !has_key(a:res, 'result')
+function! s:show_stacktrace(check_errors, res) abort
+ try
+ call a:check_errors()
+ catch
+ call go#util#EchoError(printf('could not update stack: %s', v:exception))
+ return
+ endtry
+
+ if type(a:res) isnot type({}) || !has_key(a:res, 'result') || empty(a:res.result)
return
endif
@@ -178,6 +194,9 @@ function! s:show_stacktrace(res) abort
silent %delete _
for i in range(len(a:res.result.Locations))
let loc = a:res.result.Locations[i]
+ if loc.file is# '?' || !has_key(loc, 'function')
+ continue
+ endif
call setline(i+1, printf('%s - %s:%d', loc.function.name, fnamemodify(loc.file, ':p'), loc.line))
endfor
finally
@@ -224,6 +243,7 @@ function! s:show_variables() abort
endfunction
function! s:clearState() abort
+ let s:state['running'] = 0
let s:state['currentThread'] = {}
let s:state['localVars'] = {}
let s:state['functionArgs'] = {}
@@ -233,7 +253,7 @@ function! s:clearState() abort
endfunction
function! s:stop() abort
- let l:res = s:call_jsonrpc('RPCServer.Detach', {'kill': v:true})
+ call s:call_jsonrpc(function('s:noop'), 'RPCServer.Detach', {'kill': s:state['kill_on_detach']})
if has_key(s:state, 'job')
call go#job#Wait(s:state['job'])
@@ -260,8 +280,9 @@ function! go#debug#Stop() abort
for k in map(split(execute('command GoDebug'), "\n")[1:], 'matchstr(v:val, "^\\s*\\zs\\S\\+")')
exe 'delcommand' k
endfor
- command! -nargs=* -complete=customlist,go#package#Complete GoDebugStart call go#debug#Start(0, )
- command! -nargs=* -complete=customlist,go#package#Complete GoDebugTest call go#debug#Start(1, )
+ command! -nargs=* -complete=customlist,go#package#Complete GoDebugStart call go#debug#Start('debug', )
+ command! -nargs=* -complete=customlist,go#package#Complete GoDebugTest call go#debug#Start('test', )
+ command! -nargs=1 GoDebugAttach call go#debug#Start('attach', )
command! -nargs=? GoDebugBreakpoint call go#debug#Breakpoint()
" Remove all mappings.
@@ -280,6 +301,7 @@ function! go#debug#Stop() abort
silent! exe bufwinnr(bufnr('__GODEBUG_STACKTRACE__')) 'wincmd c'
silent! exe bufwinnr(bufnr('__GODEBUG_VARIABLES__')) 'wincmd c'
silent! exe bufwinnr(bufnr('__GODEBUG_OUTPUT__')) 'wincmd c'
+ silent! exe bufwinnr(bufnr('__GODEBUG_GOROUTINES__')) 'wincmd c'
if has('balloon_eval')
let &ballooneval=s:ballooneval
@@ -326,10 +348,10 @@ endfunction
function! s:expand_var() abort
" Get name from struct line.
- let name = matchstr(getline('.'), '^[^:]\+\ze: [a-zA-Z0-9\.·]\+{\.\.\.}$')
+ let name = matchstr(getline('.'), '^[^:]\+\ze: \*\?[a-zA-Z0-9-_/\.]\+\({\.\.\.}\)\?$')
" Anonymous struct
if name == ''
- let name = matchstr(getline('.'), '^[^:]\+\ze: struct {.\{-}}$')
+ let name = matchstr(getline('.'), '^[^:]\+\ze: \*\?struct {.\{-}}$')
endif
if name != ''
@@ -419,23 +441,6 @@ function! s:start_cb() abort
endif
let debugwindows = go#config#DebugWindows()
- if has_key(debugwindows, "stack") && debugwindows['stack'] != ''
- exe 'silent ' . debugwindows['stack']
- silent file `='__GODEBUG_STACKTRACE__'`
- setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
- setlocal filetype=godebugstacktrace
- nmap :call goto_file()
- nmap q (go-debug-stop)
- endif
-
- if has_key(debugwindows, "out") && debugwindows['out'] != ''
- exe 'silent ' . debugwindows['out']
- silent file `='__GODEBUG_OUTPUT__'`
- setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
- setlocal filetype=godebugoutput
- nmap q (go-debug-stop)
- endif
-
if has_key(debugwindows, "vars") && debugwindows['vars'] != ''
exe 'silent ' . debugwindows['vars']
silent file `='__GODEBUG_VARIABLES__'`
@@ -446,24 +451,66 @@ function! s:start_cb() abort
nmap q (go-debug-stop)
endif
+ if has_key(debugwindows, "stack") && debugwindows['stack'] != ''
+ exe 'silent ' . debugwindows['stack']
+ silent file `='__GODEBUG_STACKTRACE__'`
+ setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
+ setlocal filetype=godebugstacktrace
+ nmap :call goto_file()
+ nmap q (go-debug-stop)
+ endif
+
+ if has_key(debugwindows, "goroutines") && debugwindows['goroutines'] != ''
+ exe 'silent ' . debugwindows['goroutines']
+ silent file `='__GODEBUG_GOROUTINES__'`
+ setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
+ setlocal filetype=godebugvariables
+ call append(0, ["# Goroutines"])
+ nmap :call go#debug#Goroutine()
+ endif
+
+ if has_key(debugwindows, "out") && debugwindows['out'] != ''
+ exe 'silent ' . debugwindows['out']
+ silent file `='__GODEBUG_OUTPUT__'`
+ setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
+ setlocal filetype=godebugoutput
+ nmap q (go-debug-stop)
+ endif
+ call win_gotoid(l:winid)
+
silent! delcommand GoDebugStart
silent! delcommand GoDebugTest
+ silent! delcommand GoDebugAttach
+
command! -nargs=0 GoDebugContinue call go#debug#Stack('continue')
+ command! -nargs=0 GoDebugStop call go#debug#Stop()
+
+ nnoremap (go-debug-breakpoint) :call go#debug#Breakpoint()
+ nnoremap (go-debug-continue) :call go#debug#Stack('continue')
+ nnoremap (go-debug-stop) :call go#debug#Stop()
+
+ augroup vim-go-debug
+ autocmd! *
+ autocmd FileType go nmap (go-debug-continue)
+ autocmd FileType go nmap (go-debug-breakpoint)
+ augroup END
+ doautocmd vim-go-debug FileType go
+endfunction
+
+function! s:continue()
command! -nargs=0 GoDebugNext call go#debug#Stack('next')
command! -nargs=0 GoDebugStep call go#debug#Stack('step')
command! -nargs=0 GoDebugStepOut call go#debug#Stack('stepOut')
command! -nargs=0 GoDebugRestart call go#debug#Restart()
- command! -nargs=0 GoDebugStop call go#debug#Stop()
command! -nargs=* GoDebugSet call go#debug#Set()
command! -nargs=1 GoDebugPrint call go#debug#Print()
+ command! -nargs=0 GoDebugHalt call go#debug#Stack('halt')
- nnoremap (go-debug-breakpoint) :call go#debug#Breakpoint()
nnoremap (go-debug-next) :call go#debug#Stack('next')
nnoremap (go-debug-step) :call go#debug#Stack('step')
- nnoremap (go-debug-stepout) :call go#debug#Stack('stepout')
- nnoremap (go-debug-continue) :call go#debug#Stack('continue')
- nnoremap (go-debug-stop) :call go#debug#Stop()
+ nnoremap (go-debug-stepout) :call go#debug#Stack('stepOut')
nnoremap (go-debug-print) :call go#debug#Print(expand(''))
+ nnoremap (go-debug-halt) :call go#debug#Stack('halt')
if has('balloon_eval')
let s:balloonexpr=&balloonexpr
@@ -473,8 +520,6 @@ function! s:start_cb() abort
set ballooneval
endif
- call win_gotoid(l:winid)
-
augroup vim-go-debug
autocmd! *
autocmd FileType go nmap (go-debug-continue)
@@ -482,13 +527,14 @@ function! s:start_cb() abort
autocmd FileType go nmap (go-debug-breakpoint)
autocmd FileType go nmap (go-debug-next)
autocmd FileType go nmap (go-debug-step)
+ autocmd FileType go nmap (go-debug-halt)
augroup END
doautocmd vim-go-debug FileType go
endfunction
function! s:err_cb(ch, msg) abort
if get(s:state, 'ready', 0) != 0
- call call('s:logger', ['ERR: ', a:ch, a:msg])
+ call s:logger('ERR: ', a:ch, a:msg)
return
endif
@@ -497,20 +543,21 @@ endfunction
function! s:out_cb(ch, msg) abort
if get(s:state, 'ready', 0) != 0
- call call('s:logger', ['OUT: ', a:ch, a:msg])
+ call s:logger('OUT: ', a:ch, a:msg)
return
endif
let s:state['message'] += [a:msg]
if stridx(a:msg, go#config#DebugAddress()) != -1
+ let s:state['data'] = []
+ let l:state = {'databuf': ''}
+
+ " explicitly bind callback to state so that within it, self will
+ " always refer to state. See :help Partial for more information.
+ let l:state.on_data = function('s:on_data', [], l:state)
+
if has('nvim')
- let s:state['data'] = []
- let l:state = {'databuf': ''}
-
- " explicitly bind callback to state so that within it, self will
- " always refer to state. See :help Partial for more information.
- let l:state.on_data = function('s:on_data', [], l:state)
let l:ch = sockconnect('tcp', go#config#DebugAddress(), {'on_data': l:state.on_data, 'state': l:state})
if l:ch == 0
call go#util#EchoError("could not connect to debugger")
@@ -518,7 +565,7 @@ function! s:out_cb(ch, msg) abort
return
endif
else
- let l:ch = ch_open(go#config#DebugAddress(), {'mode': 'raw', 'timeout': 20000})
+ let l:ch = ch_open(go#config#DebugAddress(), {'mode': 'raw', 'timeout': 20000, 'callback': l:state.on_data})
if ch_status(l:ch) !=# 'open'
call go#util#EchoError("could not connect to debugger")
call go#job#Stop(s:state['job'])
@@ -534,9 +581,8 @@ function! s:out_cb(ch, msg) abort
let s:state['ready'] = 1
" replace all the breakpoints set before delve started so that the ids won't overlap.
- let l:breakpoints = s:list_breakpoints()
for l:bt in s:list_breakpoints()
- exe 'sign unplace '. l:bt.id
+ call s:sign_unplace(l:bt.id, l:bt.file)
call go#debug#Breakpoint(l:bt.line, l:bt.file)
endfor
@@ -544,27 +590,108 @@ function! s:out_cb(ch, msg) abort
endif
endfunction
-function! s:on_data(ch, data, event) dict abort
- let l:data = self.databuf
- for l:msg in a:data
- let l:data .= l:msg
- endfor
+" s:on_data's third optional argument is provided, but not used, so that the
+" same function can be used for Vim's 'callback' and Neovim's 'data'.
+function! s:on_data(ch, data, ...) dict abort
+ let l:data = s:message(self.databuf, a:data)
+ let l:messages = split(l:data, "\n")
+ for l:msg in l:messages
+ let l:data = l:messages[0]
+ try
+ let l:res = json_decode(l:data)
+ " remove the decoded message
+ call remove(l:messages, 0)
+ catch
+ return
+ finally
+ " Rejoin messages and assign to databuf so that any messages that come
+ " in if s:handleRPCResult sleeps will be appended correctly.
+ "
+ " Because the current message is removed in the try immediately after
+ " decoding, that l:messages contains all the messages that have not
+ " yet been decoded including the current message if decoding it
+ " failed.
+ let self.databuf = join(l:messages, "\n")
+ endtry
+
+ if go#util#HasDebug('debugger-commands')
+ let g:go_debug_commands = add(go#config#DebugCommands(), {
+ \ 'response': l:data,
+ \ })
+ endif
+ call s:handleRPCResult(l:res)
+ endfor
+endfunction
+
+function! s:message(buf, data) abort
+ if has('nvim')
+ " dealing with the channel lines of Neovim is awful. The docs (:help
+ " channel-lines) say:
+ " stream event handlers may receive partial (incomplete) lines. For a
+ " given invocation of on_stdout etc, `a:data` is not guaranteed to end
+ " with a newline.
+ " - `abcdefg` may arrive as `['abc']`, `['defg']`.
+ " - `abc\nefg` may arrive as `['abc', '']`, `['efg']` or `['abc']`,
+ " `['','efg']`, or even `['ab']`, `['c','efg']`.
+ "
+ " Thankfully, though, this is explained a bit better in an issue:
+ " https://github.com/neovim/neovim/issues/3555. Specifically in these two
+ " comments:
+ " * https://github.com/neovim/neovim/issues/3555#issuecomment-152290804
+ " * https://github.com/neovim/neovim/issues/3555#issuecomment-152588749
+ "
+ " The key is
+ " Every item in the list passed to job control callbacks represents a
+ " string after a newline(Except the first, of course). If the program
+ " outputs: "hello\nworld" the corresponding list is ["hello", "world"].
+ " If the program outputs "hello\nworld\n", the corresponding list is
+ " ["hello", "world", ""]. In other words, you can always determine if
+ " the last line received is complete or not.
+ " and
+ " for every list you receive in a callback, all items except the first
+ " represent newlines.
+
+ let l:data = printf('%s%s', a:buf, a:data[0])
+ for l:msg in a:data[1:]
+ let l:data = printf("%s\n%s", l:data, l:msg)
+ endfor
+
+ return l:data
+ endif
+
+ return printf('%s%s', a:buf, a:data)
+endfunction
+
+" s:check_errors will be curried and injected into rpc result handlers so that
+" those result handlers can consistently check for errors in the response by
+" catching exceptions and handling the error appropriately.
+function! s:check_errors(resp_json) abort
+ if type(a:resp_json) == v:t_dict && has_key(a:resp_json, 'error') && !empty(a:resp_json.error)
+ throw a:resp_json.error
+ endif
+endfunction
+
+function! s:handleRPCResult(resp) abort
try
- let l:res = json_decode(l:data)
- let s:state['data'] = add(s:state['data'], l:res)
- let self.databuf = ''
+ let l:id = a:resp.id
+ " call the result handler with its first argument set to a curried
+ " s:check_errors value so that the result handler can call s:check_errors
+ " without passing any arguments to check whether the response is an error
+ " response.
+ call call(s:state.resultHandlers[l:id], [function('s:check_errors', [a:resp]), a:resp])
catch
- " there isn't a complete message in databuf: buffer l:data and try
- " again when more data comes in.
- let self.databuf = l:data
+ throw v:exception
finally
+ if has_key(s:state.resultHandlers, l:id)
+ call remove(s:state.resultHandlers, l:id)
+ endif
endtry
endfunction
" Start the debug mode. The first argument is the package name to compile and
" debug, anything else will be passed to the running program.
-function! go#debug#Start(is_test, ...) abort
+function! go#debug#Start(mode, ...) abort
call go#cmd#autowrite()
if !go#util#has_job()
@@ -577,7 +704,7 @@ function! go#debug#Start(is_test, ...) abort
return s:state['job']
endif
- let s:start_args = a:000
+ let s:start_args = [a:mode] + a:000
if go#util#HasDebug('debugger-state')
call go#config#SetDebugDiag(s:state)
@@ -589,39 +716,21 @@ function! go#debug#Start(is_test, ...) abort
endif
try
- if len(a:000) > 0
- let l:pkgname = a:1
- if l:pkgname[0] == '.'
- let l:pkgname = go#package#FromPath(l:pkgname)
- endif
+
+ let l:cmd = [dlv, a:mode]
+
+ let s:state['kill_on_detach'] = v:true
+ if a:mode is 'debug' || a:mode is 'test'
+ let l:cmd = extend(l:cmd, s:package(a:000))
+ let l:cmd = extend(l:cmd, ['--output', tempname()])
+ elseif a:mode is 'attach'
+ let l:cmd = add(l:cmd, a:1)
+ let s:state['kill_on_detach'] = v:false
else
- let l:pkgname = go#package#FromPath(getcwd())
+ call go#util#EchoError('Unknown dlv command')
endif
- if l:pkgname is -1
- call go#util#EchoError('could not determine package name')
- return
- endif
-
- " cd in to test directory; this is also what running "go test" does.
- if a:is_test
- " TODO(bc): Either remove this if it's ok to do so or else record it and
- " reset cwd after the job completes.
- lcd %:p:h
- endif
-
- let s:state.is_test = a:is_test
-
- let l:args = []
- if len(a:000) > 1
- let l:args = ['--'] + a:000[1:]
- endif
-
- let l:cmd = [
- \ dlv,
- \ (a:is_test ? 'test' : 'debug'),
- \ l:pkgname,
- \ '--output', tempname(),
+ let l:cmd += [
\ '--headless',
\ '--api-version', '2',
\ '--listen', go#config#DebugAddress(),
@@ -635,7 +744,10 @@ function! go#debug#Start(is_test, ...) abort
if buildtags isnot ''
let l:cmd += ['--build-flags', '--tags=' . buildtags]
endif
- let l:cmd += l:args
+
+ if len(a:000) > 1
+ let l:cmd += ['--'] + a:000[1:]
+ endif
let s:state['message'] = []
let l:opts = {
@@ -650,13 +762,46 @@ function! go#debug#Start(is_test, ...) abort
let s:state['job'] = go#job#Start(l:cmd, l:opts)
catch
- call go#util#EchoError(v:exception)
+ call go#util#EchoError(printf('could not start debugger: %s', v:exception))
endtry
return s:state['job']
endfunction
-" Translate a reflect kind constant to a human string.
+" s:package returns the import path of package name of a :GoDebug(Start|Test)
+" call as a list so that the package can be appended to a command list using
+" extend(). args is expected to be a (potentially empty_ list. The first
+" element in args (if there are any) is expected to be a package path. An
+" emnpty list is returned when either args is an empty list or the import path
+" cannot be determined.
+function! s:package(args)
+ if len(a:args) == 0
+ return []
+ endif
+
+ " append the package when it's given.
+ let l:pkgname = a:args[0]
+ if l:pkgname[0] == '.'
+ let l:pkgabspath = fnamemodify(l:pkgname, ':p')
+
+ let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
+ let l:dir = getcwd()
+ let l:dir = go#util#Chdir(expand('%:p:h'))
+ try
+ let l:pkgname = go#package#FromPath(l:pkgabspath)
+ if type(l:pkgname) == type(0)
+ call go#util#EchoError('could not determine package name')
+ return []
+ endif
+ finally
+ call go#util#Chdir(l:dir)
+ endtry
+ endif
+
+ return [l:pkgname]
+endfunction
+
+ " Translate a reflect kind constant to a human string.
function! s:reflect_kind(k)
" Kind constants from Go's reflect package.
return [
@@ -746,18 +891,25 @@ endfunction
function! s:eval(arg) abort
try
- let l:res = s:call_jsonrpc('RPCServer.State')
- let l:res = s:call_jsonrpc('RPCServer.Eval', {
+ let l:promise = go#promise#New(function('s:rpc_response'), 20000, {})
+ call s:call_jsonrpc(l:promise.wrapper, 'RPCServer.State')
+ let l:res = l:promise.await()
+ let l:promise = go#promise#New(function('s:rpc_response'), 20000, {})
+ call s:call_jsonrpc(l:promise.wrapper, 'RPCServer.Eval', {
\ 'expr': a:arg,
\ 'scope': {'GoroutineID': l:res.result.State.currentThread.goroutineID}
\ })
+
+ let l:res = l:promise.await()
+
return s:eval_tree(l:res.result.Variable, 0)
catch
- call go#util#EchoError(v:exception)
+ call go#util#EchoError(printf('evaluation failed: %s', v:exception))
return ''
endtry
endfunction
+
function! go#debug#BalloonExpr() abort
silent! let l:v = s:eval(v:beval_text)
return l:v
@@ -767,7 +919,106 @@ function! go#debug#Print(arg) abort
try
echo substitute(s:eval(a:arg), "\n$", "", 0)
catch
- call go#util#EchoError(v:exception)
+ call go#util#EchoError(printf('could not print: %s', v:exception))
+ endtry
+endfunction
+
+function! s:update_goroutines() abort
+ call s:call_jsonrpc(function('s:update_goroutines_state_handler'), 'RPCServer.State')
+endfunction
+
+function! s:update_goroutines_state_handler(check_errors, res) abort
+ try
+ call a:check_errors()
+
+ let l:currentGoroutineID = 0
+ try
+ if type(a:res) is type({}) && has_key(a:res, 'result') && !empty(a:res['result'])
+ let l:currentGoroutineID = a:res["result"]["State"]["currentGoroutine"]["id"]
+ endif
+ catch
+ call go#util#EchoWarning("current goroutine not found...")
+ endtry
+
+ call s:call_jsonrpc(function('s:list_goroutines_handler', [l:currentGoroutineID]), 'RPCServer.ListGoroutines')
+ catch
+ call go#util#EchoError(printf('could not list goroutines: %s', v:exception))
+ endtry
+endfunction
+
+function s:list_goroutines_handler(currentGoroutineID, check_errors, res) abort
+ try
+ call a:check_errors()
+ call s:show_goroutines(a:currentGoroutineID, a:res)
+ catch
+ call go#util#EchoError(printf('could not show goroutines: %s', v:exception))
+ endtry
+endfunction
+
+function! s:show_goroutines(currentGoroutineID, res) abort
+ let l:goroutines_winid = bufwinid('__GODEBUG_GOROUTINES__')
+ if l:goroutines_winid == -1
+ return
+ endif
+
+ let l:winid = win_getid()
+ call win_gotoid(l:goroutines_winid)
+
+ try
+ setlocal modifiable
+ silent %delete _
+
+ let v = ['# Goroutines']
+
+ if type(a:res) isnot type({}) || !has_key(a:res, 'result') || empty(a:res['result'])
+ call setline(1, v)
+ return
+ endif
+
+ let l:goroutines = a:res["result"]["Goroutines"]
+ if len(l:goroutines) == 0
+ call go#util#EchoWarning("No Goroutines Running Now...")
+ call setline(1, v)
+ return
+ endif
+
+ for l:idx in range(len(l:goroutines))
+ let l:goroutine = l:goroutines[l:idx]
+ let l:goroutineType = ""
+ let l:loc = 0
+ if l:goroutine.startLoc.file != ""
+ let l:loc = l:goroutine.startLoc
+ let l:goroutineType = "Start"
+ endif
+ if l:goroutine.goStatementLoc.file != ""
+ let l:loc = l:goroutine.goStatementLoc
+ let l:goroutineType = "Go"
+ endif
+ if l:goroutine.currentLoc.file != ""
+ let l:loc = l:goroutine.currentLoc
+ let l:goroutineType = "Runtime"
+ endif
+ if l:goroutine.userCurrentLoc.file != ""
+ let l:loc=l:goroutine.userCurrentLoc
+ let l:goroutineType = "User"
+ endif
+
+ " The current goroutine can be changed by pressing enter on one of the
+ " lines listing a non-active goroutine. If the format of either of these
+ " lines is modified, then make sure that go#debug#Goroutine is also
+ " changed if needed.
+ if l:goroutine.id == a:currentGoroutineID
+ let l:g = printf("* Goroutine %s - %s: %s:%s %s (thread: %s)", l:goroutine.id, l:goroutineType, l:loc.file, l:loc.line, l:loc.function.name, l:goroutine.threadID)
+ else
+ let l:g = printf(" Goroutine %s - %s: %s:%s %s (thread: %s)", l:goroutine.id, l:goroutineType, l:loc.file, l:loc.line, l:loc.function.name, l:goroutine.threadID)
+ endif
+ let v += [l:g]
+ endfor
+
+ call setline(1, v)
+ finally
+ setlocal nomodifiable
+ call win_gotoid(l:winid)
endtry
endfunction
@@ -778,22 +1029,47 @@ function! s:update_variables() abort
" MaxArrayValues is the maximum number of elements read from an array, a slice or a map.
" MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields.
let l:cfg = {
- \ 'scope': {'GoroutineID': s:groutineID()},
+ \ 'scope': {'GoroutineID': s:goroutineID()},
\ 'cfg': {'MaxStringLen': 20, 'MaxArrayValues': 20}
\ }
try
- let res = s:call_jsonrpc('RPCServer.ListLocalVars', l:cfg)
- let s:state['localVars'] = res.result['Variables']
+ call s:call_jsonrpc(function('s:handle_list_local_vars'), 'RPCServer.ListLocalVars', l:cfg)
catch
- call go#util#EchoError(v:exception)
+ call go#util#EchoError(printf('could not list variables: %s', v:exception))
endtry
try
- let res = s:call_jsonrpc('RPCServer.ListFunctionArgs', l:cfg)
- let s:state['functionArgs'] = res.result['Args']
+ call s:call_jsonrpc(function('s:handle_list_function_args'), 'RPCServer.ListFunctionArgs', l:cfg)
catch
- call go#util#EchoError(v:exception)
+ call go#util#EchoError(printf('could not list function arguments: %s', v:exception))
+ endtry
+
+endfunction
+
+function! s:handle_list_local_vars(check_errors, res) abort
+ let s:state['localVars'] = {}
+ try
+ call a:check_errors()
+ if type(a:res) is type({}) && has_key(a:res, 'result') && !empty(a:res.result)
+ let s:state['localVars'] = a:res.result['Variables']
+ endif
+ catch
+ call go#util#EchoWarning(printf('could not list variables: %s', v:exception))
+ endtry
+
+ call s:show_variables()
+endfunction
+
+function! s:handle_list_function_args(check_errors, res) abort
+ let s:state['functionArgs'] = {}
+ try
+ call a:check_errors()
+ if type(a:res) is type({}) && has_key(a:res, 'result') && !empty(a:res.result)
+ let s:state['functionArgs'] = a:res.result['Args']
+ endif
+ catch
+ call go#util#EchoWarning(printf('could not list function arguments: %s', v:exception))
endtry
call s:show_variables()
@@ -801,14 +1077,27 @@ endfunction
function! go#debug#Set(symbol, value) abort
try
- let l:res = s:call_jsonrpc('RPCServer.State')
- call s:call_jsonrpc('RPCServer.Set', {
+ let l:promise = go#promise#New(function('s:rpc_response'), 20000, {})
+ call s:call_jsonrpc(l:promise.wrapper, 'RPCServer.State')
+ let l:res = l:promise.await()
+
+ call s:call_jsonrpc(function('s:handle_set'), 'RPCServer.Set', {
\ 'symbol': a:symbol,
\ 'value': a:value,
\ 'scope': {'GoroutineID': l:res.result.State.currentThread.goroutineID}
\ })
catch
- call go#util#EchoError(v:exception)
+ call go#util#EchoError(printf('could not set symbol value: %s', v:exception))
+ endtry
+
+ call s:update_variables()
+endfunction
+
+function! s:handle_set(check_errors, res) abort
+ try
+ call a:check_errors()
+ catch
+ call go#util#EchoError(printf('could not set symbol value: %s', v:exception))
endtry
call s:update_variables()
@@ -816,20 +1105,25 @@ endfunction
function! s:update_stacktrace() abort
try
- let l:res = s:call_jsonrpc('RPCServer.Stacktrace', {'id': s:groutineID(), 'depth': 5})
- call s:show_stacktrace(l:res)
+ call s:call_jsonrpc(function('s:show_stacktrace'), 'RPCServer.Stacktrace', {'id': s:goroutineID(), 'depth': 5})
catch
- call go#util#EchoError(v:exception)
+ call go#util#EchoError(printf('could not update stack: %s', v:exception))
endtry
endfunction
function! s:stack_cb(res) abort
let s:stack_name = ''
- if empty(a:res) || !has_key(a:res, 'result')
+ if type(a:res) isnot type({}) || !has_key(a:res, 'result') || empty(a:res.result)
+ return
+ endif
+
+ if s:exited(a:res)
+ call go#debug#Stop()
return
endif
call s:update_breakpoint(a:res)
+ call s:update_goroutines()
call s:update_stacktrace()
call s:update_variables()
endfunction
@@ -844,9 +1138,11 @@ function! go#debug#Stack(name) abort
if s:state.running is 0
let s:state.running = 1
let l:name = 'continue'
+ call s:continue()
endif
" Add a breakpoint to the main.Main if the user didn't define any.
+ " TODO(bc): actually set set the breakpoint in main.Main
if len(s:list_breakpoints()) is 0
if go#debug#Breakpoint() isnot 0
let s:state.running = 0
@@ -855,21 +1151,67 @@ function! go#debug#Stack(name) abort
endif
try
- " TODO: document why this is needed.
+ " s:stack_name is reset in s:stack_cb(). While its value is 'next', the
+ " current operation being performed by delve is a next operation and it
+ " must be cancelled before another next operation can start. See
+ " https://github.com/go-delve/delve/blob/ab5713d3ec5d12754f4b2edf85e4b36a08b67c48/Documentation/api/ClientHowto.md#special-continue-commands-and-asynchronous-breakpoints
+ " for more information.
if l:name is# 'next' && get(s:, 'stack_name', '') is# 'next'
- call s:call_jsonrpc('RPCServer.CancelNext')
+ " use s:rpc_response so that the any errors will be checked instead of
+ " completely discarding the result with s:noop.
+ let l:promise = go#promise#New(function('s:rpc_response'), 20000, {})
+ call s:call_jsonrpc(l:promise.wrapper, 'RPCServer.CancelNext')
+ call l:promise.await()
endif
let s:stack_name = l:name
try
- let res = s:call_jsonrpc('RPCServer.Command', {'name': l:name})
- call s:stack_cb(res)
+ silent! sign unplace 9999
+ call s:call_jsonrpc(function('s:handle_stack_response', [l:name]), 'RPCServer.Command', {'name': l:name})
catch
- call go#util#EchoError(v:exception)
+ call go#util#EchoError(printf('rpc failure: %s', v:exception))
call s:clearState()
+ call go#util#EchoInfo('restarting debugger')
call go#debug#Restart()
endtry
catch
- call go#util#EchoError(v:exception)
+ call go#util#EchoError(printf('CancelNext RPC call failed: %s', v:exception))
+ endtry
+endfunction
+
+function! s:handle_stack_response(command, check_errors, res) abort
+ try
+ call a:check_errors()
+
+ if a:command is# 'next'
+ call s:handleNextInProgress(a:res)
+ endif
+
+ call s:stack_cb(a:res)
+ catch
+ call go#util#EchoError(printf('rpc failure: %s', v:exception))
+ call s:clearState()
+ call go#util#EchoInfo('restarting debugger')
+ call go#debug#Restart()
+ endtry
+endfunction
+
+function! s:handleNextInProgress(res)
+ try
+ let l:res = a:res
+ let l:w = 0
+ while l:w < 1
+ if l:res.result.State.NextInProgress == v:true
+ " TODO(bc): message the user that a breakpoint was hit in a different
+ " goroutine while trying to resume.
+ let l:promise = go#promise#New(function('s:rpc_response'), 20000, {})
+ call s:call_jsonrpc(l:promise.wrapper, 'RPCServer.Command', {'name': 'continue'})
+ let l:res = l:promise.await()
+ else
+ return
+ endif
+ endwhile
+ catch
+ throw v:exception
endtry
endfunction
@@ -880,17 +1222,19 @@ function! go#debug#Restart() abort
call s:stop()
let s:state = {
- \ 'rpcid': 1,
- \ 'running': 0,
- \ 'currentThread': {},
- \ 'localVars': {},
- \ 'functionArgs': {},
- \ 'message': [],
- \}
+ \ 'rpcid': 1,
+ \ 'running': 0,
+ \ 'currentThread': {},
+ \ 'localVars': {},
+ \ 'functionArgs': {},
+ \ 'message': [],
+ \ 'resultHandlers': {},
+ \ 'kill_on_detach': s:state['kill_on_detach'],
+ \ }
call call('go#debug#Start', s:start_args)
catch
- call go#util#EchoError(v:exception)
+ call go#util#EchoError(printf('restart failed: %s', v:exception))
endtry
endfunction
@@ -899,6 +1243,25 @@ function! s:isActive()
return len(s:state['message']) > 0
endfunction
+" Change Goroutine
+function! go#debug#Goroutine() abort
+ let l:goroutineID = str2nr(substitute(getline('.'), '^ Goroutine \(.\{-1,\}\) - .*', '\1', 'g'))
+
+ if l:goroutineID <= 0
+ return
+ endif
+
+ try
+ let l:promise = go#promise#New(function('s:rpc_response'), 20000, {})
+ call s:call_jsonrpc(l:promise.wrapper, 'RPCServer.Command', {'Name': 'switchGoroutine', 'GoroutineID': l:goroutineID})
+ let l:res = l:promise.await()
+ call s:stack_cb(l:res)
+ call go#util#EchoInfo("Switched goroutine to: " . l:goroutineID)
+ catch
+ call go#util#EchoError(printf('could not switch goroutine: %s', v:exception))
+ endtry
+endfunction
+
" Toggle breakpoint. Returns 0 on success and 1 on failure.
function! go#debug#Breakpoint(...) abort
let l:filename = fnamemodify(expand('%'), ':p:gs!\\!/!')
@@ -928,64 +1291,181 @@ function! go#debug#Breakpoint(...) abort
" Remove breakpoint.
if type(l:found) == v:t_dict && !empty(l:found)
- exe 'sign unplace '. l:found.id .' file=' . l:found.file
+ call s:sign_unplace(l:found.id, l:found.file)
if s:isActive()
- let res = s:call_jsonrpc('RPCServer.ClearBreakpoint', {'id': l:found.id})
+ let l:promise = go#promise#New(function('s:rpc_response'), 20000, {})
+ call s:call_jsonrpc(l:promise.wrapper, 'RPCServer.ClearBreakpoint', {'id': l:found.id})
+ let res = l:promise.await()
endif
- " Add breakpoint.
- else
+ else " Add breakpoint
if s:isActive()
- let l:res = s:call_jsonrpc('RPCServer.CreateBreakpoint', {'Breakpoint': {'file': l:filename, 'line': l:linenr}})
- let l:bt = res.result.Breakpoint
- exe 'sign place '. l:bt.id .' line=' . l:bt.line . ' name=godebugbreakpoint file=' . l:bt.file
+ let l:promise = go#promise#New(function('s:rpc_response'), 20000, {})
+ call s:call_jsonrpc(l:promise.wrapper, 'RPCServer.CreateBreakpoint', {'Breakpoint': {'file': l:filename, 'line': l:linenr}})
+ let l:res = l:promise.await()
+ let l:bt = l:res.result.Breakpoint
+ call s:sign_place(l:bt.id, l:bt.file, l:bt.line)
else
let l:id = len(s:list_breakpoints()) + 1
- exe 'sign place ' . l:id . ' line=' . l:linenr . ' name=godebugbreakpoint file=' . l:filename
+ call s:sign_place(l:id, l:filename, l:linenr)
endif
endif
catch
- call go#util#EchoError(v:exception)
+ call go#util#EchoError(printf('could not toggle breakpoint: %s', v:exception))
return 1
endtry
return 0
endfunction
+function! s:sign_unplace(id, file) abort
+ if !exists('*sign_unplace')
+ exe 'sign unplace ' . a:id .' file=' . a:file
+ return
+ endif
+
+ call sign_unplace('vim-go-debug', {'buffer': a:file, 'id': a:id})
+endfunction
+
+function! s:sign_place(id, expr, lnum) abort
+ if !exists('*sign_place')
+ exe 'sign place ' . a:id . ' line=' . a:lnum . ' name=godebugbreakpoint file=' . a:expr
+ return
+ endif
+
+ call sign_place(a:id, 'vim-go-debug', 'godebugbreakpoint', a:expr, {'lnum': a:lnum})
+endfunction
+
function! s:list_breakpoints()
- " :sign place
- " --- Signs ---
- " Signs for a.go:
- " line=15 id=2 name=godebugbreakpoint
- " line=16 id=1 name=godebugbreakpoint
- " Signs for a_test.go:
- " line=6 id=3 name=godebugbreakpoint
-
- let l:signs = []
- let l:file = ''
- for l:line in split(execute('sign place'), '\n')[1:]
- if l:line =~# '^Signs for '
- let l:file = l:line[10:-2]
- continue
- endif
-
- if l:line !~# 'name=godebugbreakpoint'
- continue
- endif
-
- let l:sign = matchlist(l:line, '\vline\=(\d+) +id\=(\d+)')
- call add(l:signs, {
- \ 'id': l:sign[2],
- \ 'file': fnamemodify(l:file, ':p'),
- \ 'line': str2nr(l:sign[1]),
- \ })
+ let l:breakpoints = []
+ let l:signs = s:sign_getplaced()
+ for l:item in l:signs
+ let l:file = fnamemodify(bufname(l:item.bufnr), ':p')
+ for l:sign in l:item.signs
+ call add(l:breakpoints, {
+ \ 'id': l:sign.id,
+ \ 'file': l:file,
+ \ 'line': l:sign.lnum,
+ \ })
+ endfor
endfor
+ return l:breakpoints
+endfunction
+
+function! s:sign_getplaced() abort
+ if !exists('*sign_getplaced') " sign_getplaced was introduced in Vim 8.1.0614
+ " :sign place
+ " --- Signs ---
+ " Signs for a.go:
+ " line=15 id=2 name=godebugbreakpoint
+ " line=16 id=1 name=godebugbreakpoint
+ " Signs for a_test.go:
+ " line=6 id=3 name=godebugbreakpoint
+
+ " l:signs should be the same sam form as the return value for
+ " sign_getplaced(), a list with the following entries:
+ " * bufnr - number of the buffer with the sign
+ " * signs = list of signs placed in bufnr
+ let l:signs = []
+ let l:file = ''
+ for l:line in split(execute('sign place'), '\n')[1:]
+ if l:line =~# '^Signs for '
+ let l:file = l:line[10:-2]
+ continue
+ else
+ " sign place's output may end with Signs instead of starting with Signs.
+ " See
+ " https://github.com/fatih/vim-go/issues/2920#issuecomment-644885774.
+ let l:idx = match(l:line, '\.go .* Signs:$')
+ if l:idx >= 0
+ let l:file = l:line[0:l:idx+2]
+ continue
+ endif
+ endif
+
+ if l:line !~# 'name=godebugbreakpoint'
+ continue
+ endif
+
+ let l:sign = matchlist(l:line, '\vline\=(\d+) +id\=(\d+)')
+ call add(l:signs, {
+ \ 'bufnr': bufnr(l:file),
+ \ 'signs': [{
+ \ 'id': str2nr(l:sign[2]),
+ \ 'lnum': str2nr(l:sign[1]),
+ \ }],
+ \ })
+ endfor
+
+ return l:signs
+ endif
+
+ " it would be nice to use lambda's here, but vim-vimparser currently fails
+ " to parse lamdas as map() arguments.
+ " TODO(bc): return flatten(map(filter(copy(getbufinfo()), { _, val -> val.listed }), { _, val -> sign_getplaced(val.bufnr, {'group': 'vim-go-debug', 'name': 'godebugbreakpoint'})}))
+ let l:bufinfo = getbufinfo()
+ let l:listed = []
+ for l:info in l:bufinfo
+ if l:info.listed
+ let l:listed = add(l:listed, l:info)
+ endif
+ endfor
+
+ let l:signs = []
+ for l:buf in l:listed
+ let l:signs = add(l:signs, sign_getplaced(l:buf.bufnr, {'group': 'vim-go-debug', 'name': 'godebugbreakpoint'})[0])
+ endfor
return l:signs
endfunction
-sign define godebugbreakpoint text=> texthl=GoDebugBreakpoint
+exe 'sign define godebugbreakpoint text='.go#config#DebugBreakpointSignText().' texthl=GoDebugBreakpoint'
sign define godebugcurline text== texthl=GoDebugCurrent linehl=GoDebugCurrent
+" s:rpc_response is a convenience function to check for errors and return
+" a:res when a:res is not an error response.
+function! s:rpc_response(check_errors, res) abort
+ call a:check_errors()
+ return a:res
+endfunction
+
+" s:noop is a noop function. It takes any number of arguments and does
+" nothing.
+function s:noop(...) abort
+endfunction
+
+function! s:warn_when_stale(filename) abort
+ let l:bufinfo = getbufinfo(a:filename)
+ if len(l:bufinfo) == 0
+ return
+ endif
+
+ if l:bufinfo[0].changed
+ call s:warn_stale()
+ return
+ endif
+
+ call s:call_jsonrpc(function('s:handle_staleness_check_response', [fnamemodify(a:filename, ':p')]), 'RPCServer.LastModified')
+endfunction
+
+function! s:handle_staleness_check_response(filename, check_errors, res) abort
+ try
+ call a:check_errors()
+ catch
+ " swallow any errors
+ return
+ endtry
+
+ let l:ftime = strftime('%Y-%m-%dT%H:%M:%S', getftime(a:filename))
+ if l:ftime < a:res.result.Time[0:(len(l:ftime) - 1)]
+ return
+ endif
+ call s:warn_stale(a:filename)
+endfunction
+
+function! s:warn_stale(filename) abort
+ call go#util#EchoWarning(printf('file locations may be incorrect, because %s has changed since debugging started', a:filename))
+endfunction
+
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/pack/acp/start/vim-go/autoload/go/debug_test.vim b/pack/acp/start/vim-go/autoload/go/debug_test.vim
index f5572e5..b82f827 100644
--- a/pack/acp/start/vim-go/autoload/go/debug_test.vim
+++ b/pack/acp/start/vim-go/autoload/go/debug_test.vim
@@ -10,6 +10,10 @@ function! Test_GoDebugStart_RelativePackage() abort
call s:debug('./debug/debugmain')
endfunction
+function! Test_GoDebugStart_RelativePackage_NullModule() abort
+ call s:debug('./debug/debugmain', 1)
+endfunction
+
function! Test_GoDebugStart_Package() abort
call s:debug('debug/debugmain')
endfunction
@@ -20,20 +24,21 @@ function! Test_GoDebugStart_Errors() abort
endif
try
+ let l:tmp = gotest#load_fixture('debug/compilerror/main.go')
+
let l:expected = [
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 0, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': '# debug/compilerror'},
- \ {'lnum': 6, 'bufnr': 7, 'col': 22, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': ' syntax error: unexpected newline, expecting comma or )'},
+ \ {'lnum': 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'}
\]
call setqflist([], 'r')
- let l:tmp = gotest#load_fixture('debug/compilerror/main.go')
call assert_false(exists(':GoDebugStop'))
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
execute l:cd . ' debug/compilerror'
- call go#debug#Start(0)
+ call go#debug#Start('debug')
let l:actual = getqflist()
let l:start = reltime()
@@ -52,14 +57,22 @@ function! Test_GoDebugStart_Errors() abort
endtry
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
if !go#util#has_job()
return
endif
try
+ let $oldgopath = $GOPATH
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 assert_false(exists(':GoDebugStop'))
@@ -67,9 +80,9 @@ function! s:debug(...) abort
if a:0 == 0
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
execute l:cd . ' debug/debugmain'
- let l:job = go#debug#Start(0)
+ let l:job = go#debug#Start('debug')
else
- let l:job = go#debug#Start(0, a:1)
+ let l:job = go#debug#Start('debug', a:1)
endif
let l:start = reltime()
diff --git a/pack/acp/start/vim-go/autoload/go/def.vim b/pack/acp/start/vim-go/autoload/go/def.vim
index 5fa1141..b8c1a9d 100644
--- a/pack/acp/start/vim-go/autoload/go/def.vim
+++ b/pack/acp/start/vim-go/autoload/go/def.vim
@@ -6,7 +6,7 @@ let s:go_stack = []
let s:go_stack_level = 0
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
" 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)
endif
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
" (they're irrelevant for gopls).
if a:type
@@ -162,9 +170,9 @@ function! go#def#jump_to_declaration(out, mode, bin_name) abort
if filename != fnamemodify(expand("%"), ':p:gs?\\?/?')
" jump to existing buffer if, 1. we have enabled it, 2. the buffer is loaded
" and 3. there is buffer window number we switch to
- if go#config#DefReuseBuffer() && bufloaded(filename) != 0 && bufwinnr(filename) != -1
- " jumpt to existing buffer if it exists
- execute bufwinnr(filename) . 'wincmd w'
+ if go#config#DefReuseBuffer() && bufwinnr(filename) != -1
+ " jump to existing buffer if it exists
+ call win_gotoid(bufwinid(filename))
else
if &modified
let cmd = 'hide edit'
@@ -186,7 +194,13 @@ function! go#def#jump_to_declaration(out, mode, bin_name) abort
endif
" 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
call cursor(line, col)
diff --git a/pack/acp/start/vim-go/autoload/go/def_test.vim b/pack/acp/start/vim-go/autoload/go/def_test.vim
index a054471..110d8c3 100644
--- a/pack/acp/start/vim-go/autoload/go/def_test.vim
+++ b/pack/acp/start/vim-go/autoload/go/def_test.vim
@@ -2,19 +2,21 @@
let s:cpo_save = &cpo
set cpo&vim
+scriptencoding utf-8
+
func! Test_jump_to_declaration_guru() abort
try
let l:filename = 'def/jump.go'
- let lnum = 5
- let col = 6
+ let l:lnum = 5
+ let l:col = 6
let l:tmp = gotest#load_fixture(l:filename)
- let guru_out = printf("%s:%d:%d: defined here as func main", filename, lnum, col)
- call go#def#jump_to_declaration(guru_out, "", 'guru')
+ 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(l:guru_out, "", 'guru')
- call assert_equal(filename, bufname("%"))
- call assert_equal(lnum, getcurpos()[1])
- call assert_equal(col, getcurpos()[2])
+ call assert_equal(l:filename, bufname("%"))
+ call assert_equal(l:lnum, getcurpos()[1])
+ call assert_equal(l:col, getcurpos()[2])
finally
call delete(l:tmp, 'rf')
endtry
@@ -22,17 +24,17 @@ endfunc
func! Test_jump_to_declaration_godef() abort
try
- let filename = 'def/jump.go'
- let lnum = 5
- let col = 6
+ let l:filename = 'def/jump.go'
+ let l:lnum = 5
+ let l:col = 6
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 assert_equal(filename, bufname("%"))
- call assert_equal(lnum, getcurpos()[1])
- call assert_equal(col, getcurpos()[2])
+ call assert_equal(l:filename, bufname("%"))
+ call assert_equal(l:lnum, getcurpos()[1])
+ call assert_equal(l:col, getcurpos()[2])
finally
call delete(l:tmp, 'rf')
endtry
@@ -40,33 +42,180 @@ endfunc
func! Test_Jump_leaves_lists() abort
try
- let filename = 'def/jump.go'
+ let l:filename = 'def/jump.go'
let l:tmp = gotest#load_fixture(l:filename)
- let expected = [{'lnum': 10, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'quux'}]
+ 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 setqflist(copy(expected), 'r' )
+ call setloclist(0, copy(l:expected), 'r' )
+ call setqflist(copy(l:expected), 'r' )
let l:bufnr = bufnr('%')
call cursor(6, 7)
+
+ if !go#util#has_job()
+ let g:go_def_mode='godef'
+ endif
call go#def#Jump('', 0)
- let start = reltime()
- while bufnr('%') == l:bufnr && reltimefloat(reltime(start)) < 10
+ if !go#util#has_job()
+ unlet g:go_def_mode
+ endif
+
+ let l:start = reltime()
+ while bufnr('%') == l:bufnr && reltimefloat(reltime(l:start)) < 10
sleep 100m
endwhile
- let actual = getloclist(winnr())
- call gotest#assert_quickfix(actual, expected)
+ let l:actual = getloclist(0)
+ call gotest#assert_quickfix(l:actual, l:expected)
- let actual = getqflist()
- call gotest#assert_quickfix(actual, expected)
+ let l:actual = getqflist()
+ call gotest#assert_quickfix(l:actual, l:expected)
finally
call delete(l:tmp, 'rf')
endtry
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
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/pack/acp/start/vim-go/autoload/go/doc.vim b/pack/acp/start/vim-go/autoload/go/doc.vim
index 5449bf7..417c231 100644
--- a/pack/acp/start/vim-go/autoload/go/doc.vim
+++ b/pack/acp/start/vim-go/autoload/go/doc.vim
@@ -6,36 +6,25 @@
let s:cpo_save = &cpo
set cpo&vim
+scriptencoding utf-8
+
let s:buf_nr = -1
function! go#doc#OpenBrowser(...) abort
- " check if we have gogetdoc as it gives us more and accurate information.
- " Only supported if we have json_decode as it's not worth to parse the plain
- " 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 len(a:000) == 0
+ let [l:out, l:err] = go#lsp#DocLink()
if l:err
- call go#util#EchoError(json_out)
+ call go#util#EchoError(l:out)
return
endif
- let out = json_decode(json_out)
- if type(out) != type({})
- call go#util#EchoError("gogetdoc output is malformed")
+ if len(l:out) == 0
+ call go#util#EchoWarning("could not path for doc URL")
endif
- let import = out["import"]
- let name = out["name"]
- let decl = out["decl"]
+ let l:godoc_url = printf('%s/%s', go#config#DocUrl(), l:out)
- let godoc_url = go#config#DocUrl()
- let godoc_url .= "/" . import
- if decl !~ "^package"
- let godoc_url .= "#" . name
- endif
-
- call go#util#OpenBrowser(godoc_url)
+ call go#util#OpenBrowser(l:godoc_url)
return
endif
@@ -48,7 +37,7 @@ function! go#doc#OpenBrowser(...) abort
let exported_name = pkgs[1]
" 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)
endfunction
@@ -56,11 +45,8 @@ function! go#doc#Open(newmode, mode, ...) abort
" With argument: run "godoc [arg]".
if len(a:000)
let [l:out, l:err] = go#util#Exec(['go', 'doc'] + a:000)
- else " Without argument: run gogetdoc on cursor position.
- let [l:out, l:err] = s:gogetdoc(0)
- if out == -1
- return
- endif
+ else " Without argument: use gopls to get documentation
+ let [l:out, l:err] = go#lsp#Doc()
endif
if l:err
@@ -68,10 +54,62 @@ function! go#doc#Open(newmode, mode, ...) abort
return
endif
- call s:GodocView(a:newmode, a:mode, out)
+ call s:GodocView(a:newmode, a:mode, l:out)
endfunction
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 :close
+ noremap :close
+ noremap q :close
+ endif
+ return
+ endif
+
" reuse existing buffer window if it exists otherwise create a new one
let is_visible = bufexists(s:buf_nr) && bufwinnr(s:buf_nr) != -1
if !bufexists(s:buf_nr)
@@ -129,23 +167,6 @@ function! s:GodocView(newposition, position, content) abort
nnoremap [ [
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.
" ie: fmt and Println
" ie: github.com/fatih/set and New
diff --git a/pack/acp/start/vim-go/autoload/go/fillstruct_test.vim b/pack/acp/start/vim-go/autoload/go/fillstruct_test.vim
index 956cd1e..73ed089 100644
--- a/pack/acp/start/vim-go/autoload/go/fillstruct_test.vim
+++ b/pack/acp/start/vim-go/autoload/go/fillstruct_test.vim
@@ -62,7 +62,7 @@ func! Test_fillstruct_two_line() abort
\ '\tAddress: "",',
\ '}) }'])
finally
- "call delete(l:tmp, 'rf')
+ call delete(l:tmp, 'rf')
endtry
endfunc
diff --git a/pack/acp/start/vim-go/autoload/go/fmt.vim b/pack/acp/start/vim-go/autoload/go/fmt.vim
index 908c0f5..d3a7d92 100644
--- a/pack/acp/start/vim-go/autoload/go/fmt.vim
+++ b/pack/acp/start/vim-go/autoload/go/fmt.vim
@@ -18,6 +18,30 @@ set cpo&vim
" this and have VimL experience, please look at the function for
" improvements, patches are welcome :)
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()
" Using winsaveview to save/restore cursor state has the problem of
" closing folds on save:
@@ -52,20 +76,16 @@ function! go#fmt#Format(withGoimport) abort
let l:tmpname = tr(l:tmpname, '\', '/')
endif
- let bin_name = go#config#FmtCommand()
- if a:withGoimport == 1
- let bin_name = "goimports"
- endif
-
let current_col = col('.')
- let [l:out, l:err] = go#fmt#run(bin_name, l:tmpname, expand('%'))
- let diff_offset = len(readfile(l:tmpname)) - line('$')
+ let [l:out, l:err] = go#fmt#run(l:bin_name, l:tmpname, expand('%'))
+ let line_offset = len(readfile(l:tmpname)) - line('$')
+ let l:orig_line = getline('.')
if l:err == 0
call go#fmt#update_file(l:tmpname, expand('%'))
elseif !go#config#FmtFailSilently()
- let errors = s:parse_errors(expand('%'), out)
- call s:show_errors(errors)
+ let l:errors = s:replace_filename(expand('%'), out)
+ call go#fmt#ShowErrors(l:errors)
endif
" We didn't use the temp file, so clean up
@@ -87,8 +107,10 @@ function! go#fmt#Format(withGoimport) abort
call winrestview(l:curw)
endif
- " be smart and jump to the line the new statement was added/removed
- call cursor(line('.') + diff_offset, current_col)
+ " be smart and jump to the line the new statement was added/removed and
+ " 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 sync fromstart
@@ -115,28 +137,12 @@ function! go#fmt#update_file(source, target)
" reload buffer to reflect latest changes
silent edit!
+ call go#lsp#DidChange(expand(a:target, ':p'))
+
let &fileformat = old_fileformat
let &syntax = &syntax
- let l:listtype = go#list#Type("GoFmt")
-
- " 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
+ call go#fmt#CleanErrors()
endfunction
" 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
endfunction
-" parse_errors parses the given errors and returns a list of parsed errors
-function! s:parse_errors(filename, content) abort
- let splitted = split(a:content, '\n')
+" replace_filename replaces the filename on each line of content with
+" a:filename.
+function! s:replace_filename(filename, content) abort
+ let l:errors = split(a:content, '\n')
- " list of errors to be put into location list
- let errors = []
- 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
+ let l:errors = map(l:errors, printf('substitute(v:val, ''^.\{-}:'', ''%s:'', '''')', a:filename))
+ return join(l:errors, "\n")
endfunction
-" show_errors opens a location list and shows the given errors. If the given
-" errors is empty, it closes the the location list
-function! s:show_errors(errors) abort
+function! go#fmt#CleanErrors() abort
let l:listtype = go#list#Type("GoFmt")
- if !empty(a:errors)
- call go#list#Populate(l:listtype, a:errors, 'Format')
- echohl Error | echomsg "Gofmt returned error" | echohl None
+
+ " 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
+ 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
- " it if there is any
- call go#list#Window(l:listtype, len(a:errors))
+ " it if there are any.
+ call go#list#Window(l:listtype, len(l:errors))
endfunction
function! go#fmt#ToggleFmtAutoSave() abort
diff --git a/pack/acp/start/vim-go/autoload/go/fmt_test.vim b/pack/acp/start/vim-go/autoload/go/fmt_test.vim
index b1b740a..d921cfc 100644
--- a/pack/acp/start/vim-go/autoload/go/fmt_test.vim
+++ b/pack/acp/start/vim-go/autoload/go/fmt_test.vim
@@ -35,7 +35,7 @@ func! Test_update_file() abort
endfunc
func! Test_goimports() abort
- let $GOPATH = 'test-fixtures/fmt/'
+ let $GOPATH = printf('%s/%s', fnamemodify(getcwd(), ':p'), 'test-fixtures/fmt')
let actual_file = tempname()
call writefile(readfile("test-fixtures/fmt/src/imports/goimports.go"), actual_file)
diff --git a/pack/acp/start/vim-go/autoload/go/guru.vim b/pack/acp/start/vim-go/autoload/go/guru.vim
index 8ff3b3f..91d5a65 100644
--- a/pack/acp/start/vim-go/autoload/go/guru.vim
+++ b/pack/acp/start/vim-go/autoload/go/guru.vim
@@ -232,11 +232,10 @@ function! go#guru#Describe(selected) abort
endfunction
function! go#guru#DescribeInfo(showstatus) abort
- " json_encode() and friends are introduced with this patch (7.4.1304)
- " vim: https://groups.google.com/d/msg/vim_dev/vLupTNhQhZ8/cDGIk0JEDgAJ
- " nvim: https://github.com/neovim/neovim/pull/4131
+
+ " check if the version of Vim being tested supports 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
endif
@@ -406,43 +405,31 @@ endfunction
" Show all refs to entity denoted by selected identifier
function! go#guru#Referrers(selected) abort
let args = {
- \ 'mode': 'referrers',
- \ 'format': 'plain',
- \ 'selected': a:selected,
- \ 'needs_scope': 0,
- \ }
+ \ 'mode': 'referrers',
+ \ 'format': 'plain',
+ \ 'selected': a:selected,
+ \ 'needs_scope': 0,
+ \ }
call s:run_guru(args)
endfunction
function! go#guru#SameIds(showstatus) abort
- " we use matchaddpos() which was introduce with 7.4.330, be sure we have
- " it: http://ftp.vim.org/vim/patches/7.4/7.4.330
+ " check if the version of Vim being tested supports matchaddpos()
if !exists("*matchaddpos")
call go#util#EchoError("GoSameIds requires 'matchaddpos'. Update your Vim/Neovim version.")
return
endif
- " json_encode() and friends are introduced with this patch (7.4.1304)
- " vim: https://groups.google.com/d/msg/vim_dev/vLupTNhQhZ8/cDGIk0JEDgAJ
- " nvim: https://github.com/neovim/neovim/pull/4131
+ " check if the version of Vim being tested supports json_decode()
if !exists("*json_decode")
call go#util#EchoError("GoSameIds requires 'json_decode'. Update your Vim/Neovim version.")
return
endif
- let args = {
- \ 'mode': 'what',
- \ 'format': 'json',
- \ '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)
+ let [l:line, l:col] = getpos('.')[1:2]
+ let [l:line, l:col] = go#lsp#lsp#Position(l:line, l:col)
+ call go#lsp#SameIDs(0, expand('%:p'), l:line, l:col, funcref('s:same_ids_highlight'))
endfunction
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
let same_ids = result['sameids']
+
" highlight the lines
+ let l:matches = []
for item in same_ids
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
+ call go#util#HighlightPositions('goSameId', l:matches)
+
if go#config#AutoSameids()
" re-apply SameIds at the current cursor position at the time the buffer
" is redisplayed: e.g. :edit, :GoRename, etc.
augroup vim-go-sameids
autocmd! *
- autocmd BufWinEnter nested call go#guru#SameIds(0)
+ if has('textprop')
+ autocmd BufReadPost nested call go#guru#SameIds(0)
+ else
+ autocmd BufWinEnter nested call go#guru#SameIds(0)
+ endif
augroup end
endif
endfunction
@@ -501,15 +496,7 @@ endfunction
" ClearSameIds returns 0 when it removes goSameId groups and non-zero if no
" goSameId groups are found.
function! go#guru#ClearSameIds() abort
- let l:cleared = 0
-
- let m = getmatches()
- for item in m
- if item['group'] == 'goSameId'
- call matchdelete(item['id'])
- let l:cleared = 1
- endif
- endfor
+ let l:cleared = go#util#ClearHighlights('goSameId')
if !l:cleared
return 1
@@ -534,11 +521,11 @@ function! go#guru#AutoToggleSameIds() abort
call go#util#EchoProgress("sameids auto highlighting disabled")
call go#guru#ClearSameIds()
call go#config#SetAutoSameids(0)
- return
+ else
+ call go#util#EchoSuccess("sameids auto highlighting enabled")
+ call go#config#SetAutoSameids(1)
endif
-
- call go#util#EchoSuccess("sameids auto highlighting enabled")
- call go#config#SetAutoSameids(1)
+ call go#auto#update_autocmd()
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 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)
call go#list#Window(l:listtype, len(errors))
-endfun
+endfunction
function! go#guru#Scope(...) abort
if a:0
@@ -602,11 +589,9 @@ function! go#guru#DescribeBalloon() abort
return
endif
- " json_encode() and friends are introduced with this patch (7.4.1304)
- " vim: https://groups.google.com/d/msg/vim_dev/vLupTNhQhZ8/cDGIk0JEDgAJ
- " nvim: https://github.com/neovim/neovim/pull/4131
+ " check if the version of Vim being tested supports 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
endif
diff --git a/pack/acp/start/vim-go/autoload/go/highlight_test.vim b/pack/acp/start/vim-go/autoload/go/highlight_test.vim
index 5693871..9360a8f 100644
--- a/pack/acp/start/vim-go/autoload/go/highlight_test.vim
+++ b/pack/acp/start/vim-go/autoload/go/highlight_test.vim
@@ -98,6 +98,325 @@ function! Test_gomodVersion_incompatible_highlight() abort
endtry
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
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/pack/acp/start/vim-go/autoload/go/impl_test.vim b/pack/acp/start/vim-go/autoload/go/impl_test.vim
index 0809836..6be39a1 100644
--- a/pack/acp/start/vim-go/autoload/go/impl_test.vim
+++ b/pack/acp/start/vim-go/autoload/go/impl_test.vim
@@ -12,7 +12,7 @@ func! Test_impl() abort
call go#impl#Impl('r', 'reader', 'io.Reader')
call gotest#assert_buffer(1, [
\ 'func (r reader) Read(p []byte) (n int, err error) {',
- \ ' panic("not implemented")',
+ \ ' panic("not implemented") // TODO: Implement',
\ '}'])
finally
call delete(l:tmp, 'rf')
@@ -33,7 +33,7 @@ func! Test_impl_get() abort
\ 'type reader struct {}',
\ '',
\ 'func (r *reader) Read(p []byte) (n int, err error) {',
- \ ' panic("not implemented")',
+ \ ' panic("not implemented") // TODO: Implement',
\ '}'])
finally
call delete(l:tmp, 'rf')
diff --git a/pack/acp/start/vim-go/autoload/go/implements.vim b/pack/acp/start/vim-go/autoload/go/implements.vim
new file mode 100644
index 0000000..0d25f8c
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/implements.vim
@@ -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
diff --git a/pack/acp/start/vim-go/autoload/go/import.vim b/pack/acp/start/vim-go/autoload/go/import.vim
index 4e84896..848a5c0 100644
--- a/pack/acp/start/vim-go/autoload/go/import.vim
+++ b/pack/acp/start/vim-go/autoload/go/import.vim
@@ -82,7 +82,7 @@ function! go#import#SwitchImport(enabled, localname, path, bang) abort
while line <= line("$")
let line = line + 1
let linestr = getline(line)
- let m = matchlist(getline(line), '^\()\|\(\s\+\)\(\S*\s*\)"\(.\+\)"\)')
+ let m = matchlist(getline(line), '^\()\|\(\s\+\)\(\w\+\s\+\)\="\(.\+\)"\)')
if empty(m)
if siteprefix == "" && a:enabled
" must be in the first group
diff --git a/pack/acp/start/vim-go/autoload/go/import_test.vim b/pack/acp/start/vim-go/autoload/go/import_test.vim
new file mode 100644
index 0000000..c4306a1
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/import_test.vim
@@ -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
diff --git a/pack/acp/start/vim-go/autoload/go/issue.vim b/pack/acp/start/vim-go/autoload/go/issue.vim
index 65db9d8..135cc25 100644
--- a/pack/acp/start/vim-go/autoload/go/issue.vim
+++ b/pack/acp/start/vim-go/autoload/go/issue.vim
@@ -18,20 +18,34 @@ function! s:issuebody() abort
for l in lines
let body = add(body, l)
- if l =~ '^\* Vim version'
- redir => out
- silent version
- redir END
+ if l =~ '^'
let [out, err] = go#util#Exec(['go', 'version'])
let body = add(body, substitute(l:out, rtrimpat, '', ''))
- elseif l =~ '^\* Go environment'
- let [out, err] = go#util#Exec(['go', 'env'])
+ elseif l =~ '^'
+ let [out, err] = go#util#ExecInDir(['go', 'env'])
+ let body = add(body, substitute(l:out, rtrimpat, '', ''))
+ elseif l=~ '^'
+ let [out, err] = go#util#Exec(['gopls', 'version'])
let body = add(body, substitute(l:out, rtrimpat, '', ''))
endif
endfor
+ let body = add(body, "\n#### vim-go configuration:\nvim-go configuration
")
+
+ for k in keys(g:)
+ if k =~ '^go_'
+ let body = add(body, 'g:' . k . ' = ' . string(get(g:, k)))
+ endif
+ endfor
+
+ let body = add(body, '
')
+
+ let body = add(body, printf("\n#### filetype detection configuration:\nfiletype detection
%s", execute('filetype')))
+ let body = add(body, '
')
+
return join(body, "\n")
endfunction
diff --git a/pack/acp/start/vim-go/autoload/go/job.vim b/pack/acp/start/vim-go/autoload/go/job.vim
index 328572e..f0ee305 100644
--- a/pack/acp/start/vim-go/autoload/go/job.vim
+++ b/pack/acp/start/vim-go/autoload/go/job.vim
@@ -41,7 +41,12 @@ endfunction
" the current window will be the window that was hosting the buffer when
" the job was started. After it returns, the current window will be
" restored to what it was before the function was called.
-
+" '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:
" 'callback':
" A function suitable to be passed as a job callback handler. See
@@ -61,7 +66,7 @@ function! go#job#Options(args)
let state = {
\ 'winid': win_getid(winnr()),
\ 'dir': getcwd(),
- \ 'jobdir': fnameescape(expand("%:p:h")),
+ \ 'jobdir': expand("%:p:h"),
\ 'messages': [],
\ 'bang': 0,
\ 'for': "_job",
@@ -69,12 +74,10 @@ function! go#job#Options(args)
\ 'exit_status': 0,
\ 'closed': 0,
\ 'errorformat': &errorformat,
- \ 'statustype' : ''
+ \ 'statustype' : '',
\ }
- if has("patch-8.0.0902") || has('nvim')
- let cbs.cwd = state.jobdir
- endif
+ let cbs.cwd = state.jobdir
if has_key(a:args, 'bang')
let state.bang = a:args.bang
@@ -92,6 +95,10 @@ function! go#job#Options(args)
let state.errorformat = a:args.errorformat
endif
+ if has_key(a:args, 'preserveerrors')
+ let state.preserveerrors = a:args.preserveerrors
+ endif
+
function state.complete(job, exit_status, data)
if has_key(self, 'custom_complete')
let l:winid = win_getid(winnr())
@@ -177,16 +184,26 @@ function! go#job#Options(args)
call win_gotoid(self.winid)
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
- call go#list#Clean(l:listtype)
- call win_gotoid(l:winid)
+ if !l:preserveerrors
+ call go#list#Clean(l:listtype)
+ call win_gotoid(l:winid)
+ endif
return
endif
let l:listtype = go#list#Type(self.for)
if len(a:data) == 0
- call go#list#Clean(l:listtype)
- call win_gotoid(l:winid)
+ if !l:preserveerrors
+ call go#list#Clean(l:listtype)
+ call win_gotoid(l:winid)
+ endif
return
endif
@@ -195,8 +212,8 @@ function! go#job#Options(args)
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
try
" parse the errors relative to self.jobdir
- execute l:cd self.jobdir
- call go#list#ParseFormat(l:listtype, self.errorformat, out, self.for)
+ execute l:cd fnameescape(self.jobdir)
+ call go#list#ParseFormat(l:listtype, self.errorformat, out, self.for, l:preserveerrors)
let errors = go#list#Get(l:listtype)
finally
execute l:cd fnameescape(self.dir)
@@ -295,11 +312,6 @@ function! go#job#Start(cmd, options)
let l:manualcd = 1
let dir = getcwd()
execute l:cd fnameescape(filedir)
- elseif !(has("patch-8.0.0902") || has('nvim'))
- let l:manualcd = 1
- let l:dir = l:options.cwd
- execute l:cd fnameescape(l:dir)
- call remove(l:options, 'cwd')
endif
if has_key(l:options, '_start')
diff --git a/pack/acp/start/vim-go/autoload/go/job_test.vim b/pack/acp/start/vim-go/autoload/go/job_test.vim
new file mode 100644
index 0000000..42f90ad
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/job_test.vim
@@ -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
diff --git a/pack/acp/start/vim-go/autoload/go/lint.vim b/pack/acp/start/vim-go/autoload/go/lint.vim
index 21e2102..248e666 100644
--- a/pack/acp/start/vim-go/autoload/go/lint.vim
+++ b/pack/acp/start/vim-go/autoload/go/lint.vim
@@ -3,30 +3,36 @@ let s:cpo_save = &cpo
set cpo&vim
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()
- if l:metalinter == 'gometalinter' || l:metalinter == 'golangci-lint'
- let cmd = s:metalintercmd(l:metalinter)
+ if a:0 == 0
+ 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)
return
endif
- " linters
- let linters = a:autosave ? go#config#MetalinterAutosaveEnabled() : go#config#MetalinterEnabled()
+ " add linters to cmd
for linter in linters
let cmd += ["--enable=".linter]
endfor
-
- for linter in go#config#MetalinterDisabled()
- let cmd += ["--disable=".linter]
- endfor
- else
+ elseif l:metalinter != 'gopls'
" the user wants something else, let us use it.
let cmd = split(go#config#MetalinterCommand(), " ")
endif
@@ -36,15 +42,8 @@ function! go#lint#Gometa(bang, autosave, ...) abort
" will be cleared
redraw
- if l:metalinter == "gometalinter"
- " Include only messages for the active buffer for autosave.
- let include = [printf('--include=^%s:.*$', fnamemodify(expand('%:p'), ":."))]
- if go#util#has_job()
- let include = [printf('--include=^%s:.*$', expand('%:p:t'))]
- endif
- let cmd += include
- elseif l:metalinter == "golangci-lint"
- let goargs[0] = expand('%:p')
+ if l:metalinter == "golangci-lint"
+ let l:goargs[0] = expand('%:p:h')
endif
endif
@@ -54,139 +53,288 @@ function! go#lint#Gometa(bang, autosave, ...) abort
let cmd += ["--deadline=" . deadline]
endif
- let cmd += goargs
+ let cmd += l:goargs
- if l:metalinter == "gometalinter"
- " Gometalinter can output one of the two, so we look for both:
- " :::: ()
- " :::: ()
- " This can be defined by the following errorformat:
- let errformat = "%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m"
+ let errformat = s:errorformat(l:metalinter)
+
+ if l:metalinter == 'gopls'
+ if a:autosave
+ let l:messages = go#lsp#AnalyzeFile(expand('%:p'))
+ 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
- " Golangci-lint can output the following:
- " ::: ()
- " This can be defined by the following errorformat:
- let errformat = "%f:%l:%c:\ %m"
- endif
+ if go#util#has_job()
+ if a:autosave
+ let l:for = 'GoMetaLinterAutoSave'
+ else
+ let l:for = 'GoMetaLinter'
+ endif
- if go#util#has_job()
- call s:lint_job({'cmd': cmd, 'statustype': l:metalinter, 'errformat': errformat}, a:bang, a:autosave)
- return
- endif
+ call s:lint_job(l:metalinter, {'cmd': cmd, 'statustype': l:metalinter, 'errformat': errformat, 'for': l:for}, a:bang, a:autosave)
+ return
+ 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
- let l:listtype = go#list#Type("GoMetaLinterAutoSave")
+ let l:listtype = go#list#Type('GoMetaLinterAutoSave')
+ let l:for = 'GoMetaLinterAutoSave'
else
- let l:listtype = go#list#Type("GoMetaLinter")
+ let l:listtype = go#list#Type('GoMetaLinter')
+ let l:for = 'GoMetaLinterAuto'
endif
if l:err == 0
- call go#list#Clean(l:listtype)
- echon "vim-go: " | echohl Function | echon "[metalinter] PASS" | echohl None
+ if !s:preserveerrors(a:autosave, l:listtype)
+ call go#list#Clean(l:listtype)
+ endif
+ call go#util#EchoSuccess('[metalinter] PASS')
else
let l:winid = win_getid(winnr())
" 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)
call go#list#Window(l:listtype, len(errors))
if a:autosave || a:bang
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
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
endfunction
" Golint calls 'golint' on the current directory. Any warnings are populated in
" the location list
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
- 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
let [l:out, l:err] = go#util#Exec([go#config#GolintBin()] + a:000)
endif
- if empty(l:out)
- call go#util#EchoSuccess('[lint] PASS')
- return
- endif
-
- let l:winid = win_getid(winnr())
+ let l:status.state = 'success'
+ let l:state = 'PASS'
let l:listtype = go#list#Type("GoLint")
- call go#list#Parse(l:listtype, l:out, "GoLint")
- let l:errors = go#list#Get(l:listtype)
- call go#list#Window(l:listtype, len(l:errors))
+ if !empty(l:out)
+ let l:status.state = 'failed'
+ let l:state = 'FAIL'
- if a:bang
- call win_gotoid(l:winid)
- return
+ let l:winid = win_getid(winnr())
+ call go#list#Parse(l:listtype, l:out, "GoLint", 0)
+ 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
-
- call go#list#JumpToFirst(l:listtype)
+ call go#statusline#Update(expand('%:p:h'), l:status)
endfunction
-" Vet calls 'go vet' on the current directory. Any warnings are populated in
-" the location list
+" Vet calls 'go vet' on the current buffer's directory. Any warnings are
+" populated in the location list
function! go#lint#Vet(bang, ...) abort
call go#cmd#autowrite()
- if go#config#EchoCommandInfo()
- call go#util#EchoProgress('calling vet...')
+ let l:cmd = ['go', 'vet']
+
+ let buildtags = go#config#BuildTags()
+ if buildtags isnot ''
+ let l:cmd += ['-tags', buildtags]
endif
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
- let [l:out, l:err] = go#util#Exec(['go', 'tool', 'vet'] + a:000)
+ let l:cmd = extend(l:cmd, a:000)
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")
if l:err != 0
+ let l:status.state = 'failed'
+ let l:state = 'FAIL'
+
let l:winid = win_getid(winnr())
- let errorformat = "%-Gexit status %\\d%\\+," . &errorformat
- call go#list#ParseFormat(l:listtype, l:errorformat, out, "GoVet")
- let errors = go#list#Get(l:listtype)
- call go#list#Window(l:listtype, len(errors))
- if !empty(errors) && !a:bang
+ let l:errorformat = "%-Gexit status %\\d%\\+," . &errorformat
+ let l:dir = go#util#Chdir(expand('%:p:h'))
+ try
+ call go#list#ParseFormat(l:listtype, l:errorformat, out, "GoVet", 0)
+ 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)
else
call win_gotoid(l:winid)
endif
+
+ if go#config#EchoCommandInfo()
+ call go#util#EchoError(printf('[%s] %s', l:type, l:state))
+ endif
else
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
+ call go#statusline#Update(expand('%:p:h'), l:status)
endfunction
" ErrCheck calls 'errcheck' for the given packages. Any warnings are populated in
" the location list
function! go#lint#Errcheck(bang, ...) abort
- if a:0 == 0
- let l:import_path = go#package#ImportPath()
- if import_path == -1
- call go#util#EchoError('package is not inside GOPATH src')
- return
- endif
- else
- let l:import_path = join(a:000, ' ')
+ call go#cmd#autowrite()
+
+ let l:cmd = [go#config#ErrcheckBin(), '-abspath']
+
+ let buildtags = go#config#BuildTags()
+ if buildtags isnot ''
+ let l:cmd += ['-tags', buildtags]
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
- 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")
if l:err != 0
- let l:winid = win_getid(winnr())
- let errformat = "%f:%l:%c:\ %m, %f:%l:%c\ %#%m"
+ let l:status.state = 'failed'
+ let l:state = 'FAIL'
- " Parse and populate our location list
- call go#list#ParseFormat(l:listtype, errformat, split(out, "\n"), 'Errcheck')
+ let l:winid = win_getid(winnr())
+
+ 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)
if empty(l:errors)
@@ -195,18 +343,24 @@ function! go#lint#Errcheck(bang, ...) abort
endif
if !empty(errors)
- call go#list#Populate(l:listtype, errors, 'Errcheck')
- call go#list#Window(l:listtype, len(errors))
+ call go#list#Populate(l:listtype, l:errors, 'Errcheck')
+ call go#list#Window(l:listtype, len(l:errors))
if !a:bang
call go#list#JumpToFirst(l:listtype)
else
call win_gotoid(l:winid)
endif
endif
+ if go#config#EchoCommandInfo()
+ call go#util#EchoError(printf('[%s] %s', l:type, l:state))
+ endif
else
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
+ call go#statusline#Update(expand('%:p:h'), l:status)
endfunction
function! go#lint#ToggleMetaLinterAutoSave() abort
@@ -220,16 +374,19 @@ function! go#lint#ToggleMetaLinterAutoSave() abort
call go#util#EchoProgress("auto metalinter enabled")
endfunction
-function! s:lint_job(args, bang, autosave)
+function! s:lint_job(metalinter, args, bang, autosave)
let l:opts = {
\ 'statustype': a:args.statustype,
\ 'errorformat': a:args.errformat,
- \ 'for': "GoMetaLinter",
+ \ 'for': 'GoMetaLinter',
\ 'bang': a:bang,
- \ }
+ \ }
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
" 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)
endfunction
-function! s:metalintercmd(metalinter)
+function! s:metalintercmd(metalinter, haslinter)
let l:cmd = []
let bin_path = go#path#CheckBinPath(a:metalinter)
if !empty(bin_path)
- if a:metalinter == "gometalinter"
- let l:cmd = s:gometalintercmd(bin_path)
- elseif a:metalinter == "golangci-lint"
- let l:cmd = s:golangcilintcmd(bin_path)
+ if a:metalinter == "golangci-lint"
+ let l:cmd = s:golangcilintcmd(bin_path, a:haslinter)
endif
endif
return cmd
endfunction
-function! s:gometalintercmd(bin_path)
- let cmd = [a:bin_path]
- let cmd += ["--disable-all"]
-
- " gometalinter has a --tests flag to tell its linters whether to run
- " against tests. While not all of its linters respect this flag, for those
- " that do, it means if we don't pass --tests, the linter won't run against
- " test files. One example of a linter that will not run against tests if
- " we do not specify this flag is errcheck.
- let cmd += ["--tests"]
- return cmd
-endfunction
-
-function! s:golangcilintcmd(bin_path)
+function! s:golangcilintcmd(bin_path, haslinter)
let cmd = [a:bin_path]
let cmd += ["run"]
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
" problems about missing doc strings to be ignored and other things that
" golint identifies.
let cmd += ["--exclude-use-default=false"]
+
+ if a:haslinter
+ let cmd += ["--disable-all"]
+ endif
+
return cmd
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:
+ " ::: ()
+ " 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
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/pack/acp/start/vim-go/autoload/go/lint_test.vim b/pack/acp/start/vim-go/autoload/go/lint_test.vim
index dccc138..6c556d3 100644
--- a/pack/acp/start/vim-go/autoload/go/lint_test.vim
+++ b/pack/acp/start/vim-go/autoload/go/lint_test.vim
@@ -2,23 +2,24 @@
let s:cpo_save = &cpo
set cpo&vim
-func! Test_Gometa() abort
- call s:gometa('gometaliner')
-endfunc
-
func! Test_GometaGolangciLint() abort
call s:gometa('golangci-lint')
endfunc
func! s:gometa(metalinter) abort
- let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
+ let RestoreGOPATH = go#util#SetEnv('GOPATH', fnamemodify(getcwd(), ':p') . 'test-fixtures/lint')
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
try
- let g:go_metalinter_comand = a:metalinter
+ let g:go_metalinter_command = a:metalinter
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'}
\ ]
+ 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
call setqflist([], 'r')
@@ -36,34 +37,32 @@ func! s:gometa(metalinter) abort
call gotest#assert_quickfix(actual, expected)
finally
+ call call(RestoreGOPATH, [])
unlet g:go_metalinter_enabled
endtry
endfunc
-func! Test_GometaWithDisabled() abort
- call s:gometawithdisabled('gometalinter')
+func! Test_GometaGolangciLint_shadow() abort
+ call s:gometa_shadow('golangci-lint')
endfunc
-func! Test_GometaWithDisabledGolangciLint() abort
- call s:gometawithdisabled('golangci-lint')
-endfunc
-
-func! s:gometawithdisabled(metalinter) abort
- let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
- silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
+func! s:gometa_shadow(metalinter) abort
+ let RestoreGOPATH = go#util#SetEnv('GOPATH', fnamemodify(getcwd(), ':p') . 'test-fixtures/lint')
+ silent exe 'e ' . $GOPATH . '/src/lint/golangci-lint/problems/shadow/problems.go'
try
- let g:go_metalinter_comand = a:metalinter
+ let g:go_metalinter_command = a:metalinter
let expected = [
- \ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'}
+ \ {'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
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 start = reltime()
@@ -74,58 +73,305 @@ func! s:gometawithdisabled(metalinter) abort
call gotest#assert_quickfix(actual, expected)
finally
- unlet g:go_metalinter_disabled
+ call call(RestoreGOPATH, [])
+ unlet g:go_metalinter_enabled
endtry
endfunc
-func! Test_GometaAutoSave() abort
- call s:gometaautosave('gometalinter')
-endfunc
-
func! Test_GometaAutoSaveGolangciLint() abort
- call s:gometaautosave('golangci-lint')
+ call s:gometaautosave('golangci-lint', 0)
endfunc
-func! s:gometaautosave(metalinter) abort
- let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
+func! Test_GometaAutoSaveKeepsErrors() abort
+ 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'
try
- let g:go_metalinter_comand = a:metalinter
- let expected = [
+ let g:go_metalinter_command = a:metalinter
+ 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)'}
\ ]
+ 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
- call setloclist(l:winnr, [], 'r')
+ " set the location lists
+ call setloclist(0, l:list, 'r')
let g:go_metalinter_autosave_enabled = ['golint']
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()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
- let actual = getloclist(l:winnr)
+ let actual = getqflist()
endwhile
call gotest#assert_quickfix(actual, expected)
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
endtry
endfunc
func! Test_Vet() abort
- let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
- silent exe 'e ' . $GOPATH . '/src/vet/vet.go'
+ let l:tmp = gotest#load_fixture('lint/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
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'}
+ \ {'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()
@@ -133,7 +379,35 @@ func! Test_Vet() abort
" clear the location lists
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 start = reltime()
@@ -145,6 +419,70 @@ func! Test_Vet() abort
call gotest#assert_quickfix(actual, expected)
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
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/pack/acp/start/vim-go/autoload/go/list.vim b/pack/acp/start/vim-go/autoload/go/list.vim
index 1805d4d..e700203 100644
--- a/pack/acp/start/vim-go/autoload/go/list.vim
+++ b/pack/acp/start/vim-go/autoload/go/list.vim
@@ -49,26 +49,23 @@ endfunction
function! go#list#Populate(listtype, items, title) abort
if a:listtype == "locationlist"
call setloclist(0, a:items, 'r')
-
- " 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
+ call setloclist(0, [], 'a', {'title': a:title})
else
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
endfunction
" Parse parses the given items based on the specified errorformat and
" 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
let old_errorformat = &errorformat
" parse and populate the location list
let &errorformat = a:errformat
try
- call go#list#Parse(a:listtype, a:items, a:title)
+ call go#list#Parse(a:listtype, a:items, a:title, a:add)
finally
"restore back
let &errorformat = old_errorformat
@@ -77,13 +74,26 @@ endfunction
" Parse parses the given items based on the global errorformat and
" 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"
- lgetexpr a:items
- if has("patch-7.4.2200") | call setloclist(0, [], 'a', {'title': a:title}) | endif
+ if a:add
+ laddexpr a:items
+ else
+ lgetexpr a:items
+ endif
+ call setloclist(0, [], 'a', {'title': a:title})
else
- cgetexpr a:items
- if has("patch-7.4.2200") | call setqflist([], 'a', {'title': a:title}) | endif
+ if a:add
+ caddexpr a:items
+ else
+ cgetexpr a:items
+ endif
+ call setqflist([], 'a', {'title': a:title})
endif
endfunction
@@ -138,6 +148,7 @@ endfunction
" in g:go_list_type_commands.
let s:default_list_type_commands = {
\ "GoBuild": "quickfix",
+ \ "GoDiagnostics": "quickfix",
\ "GoDebug": "quickfix",
\ "GoErrCheck": "quickfix",
\ "GoFmt": "locationlist",
@@ -152,6 +163,8 @@ let s:default_list_type_commands = {
\ "GoRun": "quickfix",
\ "GoTest": "quickfix",
\ "GoVet": "quickfix",
+ \ "GoReferrers": "locationlist",
+ \ "GoImplements": "locationlist",
\ "_guru": "locationlist",
\ "_term": "locationlist",
\ "_job": "locationlist",
diff --git a/pack/acp/start/vim-go/autoload/go/lsp.vim b/pack/acp/start/vim-go/autoload/go/lsp.vim
index aa21bce..6b08acf 100644
--- a/pack/acp/start/vim-go/autoload/go/lsp.vim
+++ b/pack/acp/start/vim-go/autoload/go/lsp.vim
@@ -7,8 +7,7 @@ scriptencoding utf-8
let s:lspfactory = {}
function! s:lspfactory.get() dict abort
- if !has_key(self, 'current')
- " TODO(bc): check that the lsp is still running.
+ if empty(get(self, 'current', {})) || empty(get(self.current, 'job', {}))
let self.current = s:newlsp()
endif
@@ -22,12 +21,6 @@ function! s:lspfactory.reset() dict abort
endfunction
function! s:newlsp() abort
- if !go#util#has_job()
- " TODO(bc): start the server in the background using a shell that waits for the right output before returning.
- call go#util#EchoError('This feature requires either Vim 8.0.0087 or newer with +job or Neovim.')
- return
- endif
-
" job is the job used to talk to the backing instance of gopls.
" ready is 0 until the initialize response has been received. 1 afterwards.
" queue is messages to send after initialization
@@ -41,6 +34,18 @@ function! s:newlsp() abort
" * handleResult takes a single argument, the result message received from gopls
" * error takes a single argument, the error message received from gopls.
" The error method is optional.
+ " workspaceDirectories is an array of named workspaces.
+ " wd is the working directory for gopls
+ " diagnostics is a dictionary whose keys are filenames and each value is a
+ " list of diagnostic messages for the file.
+ " diagnosticsQueue is a queue of diagnostics notifications that have been
+ " received, but not yet processed.
+ " fileVersions is a dictionary of filenames to versions.
+ " notificationQueue is a dictionary of filenames to functions. For a given
+ " filename, each notification will call the first function in the list of
+ " function values and remove it from the list. The functions should accept
+ " two arguments: an absolute path and a list of diagnotics messages for
+ " the file.
let l:lsp = {
\ 'job': '',
\ 'ready': 0,
@@ -48,8 +53,32 @@ function! s:newlsp() abort
\ 'last_request_id': 0,
\ 'buf': '',
\ 'handlers': {},
+ \ 'workspaceDirectories': [],
+ \ 'wd' : '',
+ \ 'diagnosticsQueue': [],
+ \ 'diagnostics': {},
+ \ 'fileVersions': {},
+ \ 'notificationQueue': {},
\ }
+ if !go#config#GoplsEnabled()
+ let l:lsp.sendMessage = funcref('s:noop')
+ return l:lsp
+ endif
+
+ if !go#util#has_job()
+ let l:oldshortmess=&shortmess
+ if has('nvim')
+ set shortmess-=F
+ endif
+ call go#util#EchoWarning('Features that rely on gopls will not work without either Vim 8.0.0087 or newer with +job or Neovim')
+ " Sleep one second to make sure people see the message. Otherwise it is
+ " often immediately overwritten by an async message.
+ sleep 1
+ let &shortmess=l:oldshortmess
+ return l:lsp
+ endif
+
function! l:lsp.readMessage(data) dict abort
let l:responses = []
let l:rest = a:data
@@ -76,19 +105,17 @@ function! s:newlsp() abort
endif
" get the start of the rest
- let l:rest_start_idx = l:body_start_idx + str2nr(l:length_match[1])
+ let l:next_start_idx = l:body_start_idx + str2nr(l:length_match[1])
- if len(l:rest) < l:rest_start_idx
+ if len(l:rest) < l:next_start_idx
" incomplete response body
break
endif
- if go#util#HasDebug('lsp')
- let g:go_lsp_log = add(go#config#LspLog(), "<-\n" . l:rest[:l:rest_start_idx - 1])
- endif
+ call s:debug('received', l:rest[:l:next_start_idx - 1])
- let l:body = l:rest[l:body_start_idx : l:rest_start_idx - 1]
- let l:rest = l:rest[l:rest_start_idx :]
+ let l:body = l:rest[l:body_start_idx : l:next_start_idx - 1]
+ let l:rest = l:rest[l:next_start_idx :]
try
" add the json body to the list.
@@ -106,36 +133,176 @@ function! s:newlsp() abort
function! l:lsp.handleMessage(ch, data) dict abort
let self.buf .= a:data
- let [self.buf, l:responses] = self.readMessage(self.buf)
+ let [self.buf, l:messages] = self.readMessage(self.buf)
- " TODO(bc): handle notifications (e.g. window/showMessage).
-
- for l:response in l:responses
- if has_key(l:response, 'id') && has_key(self.handlers, l:response.id)
- try
- let l:handler = self.handlers[l:response.id]
-
- if has_key(l:response, 'error')
- call l:handler.requestComplete(0)
- if has_key(l:handler, 'error')
- call call(l:handler.error, [l:response.error.message])
- else
- call go#util#EchoError(l:response.error.message)
- endif
- return
- endif
- call l:handler.requestComplete(1)
- call call(l:handler.handleResult, [l:response.result])
- finally
- call remove(self.handlers, l:response.id)
- endtry
+ for l:message in l:messages
+ if has_key(l:message, 'method')
+ if has_key(l:message, 'id')
+ call self.handleRequest(l:message)
+ else
+ call self.handleNotification(l:message)
+ endif
+ elseif has_key(l:message, 'result') || has_key(l:message, 'error')
+ call self.handleResponse(l:message)
endif
endfor
endfunction
+ function! l:lsp.handleRequest(req) dict abort
+ if a:req.method == 'workspace/workspaceFolders'
+ let l:resp = go#lsp#message#WorkspaceFoldersResult(self.workspaceDirectories)
+ elseif a:req.method == 'workspace/configuration' && has_key(a:req, 'params') && has_key(a:req.params, 'items')
+ let l:resp = go#lsp#message#ConfigurationResult(a:req.params.items)
+ elseif a:req.method == 'client/registerCapability' && has_key(a:req, 'params') && has_key(a:req.params, 'registrations')
+ let l:resp = v:null
+ else
+ return
+ endif
+
+ if get(self, 'exited', 0)
+ return
+ endif
+
+ let l:msg = self.newResponse(a:req.id, l:resp)
+ call self.write(l:msg)
+ endfunction
+
+ function! l:lsp.handleResponse(resp) dict abort
+ if has_key(a:resp, 'id') && has_key(self.handlers, a:resp.id)
+ try
+ let l:handler = self.handlers[a:resp.id]
+
+ let l:winid = win_getid(winnr())
+ " Always set the active window to the window that was active when
+ " the request was sent. Among other things, this makes sure that
+ " the correct window's location list will be populated when the
+ " list type is 'location' and the user has moved windows since
+ " sending the request.
+ call win_gotoid(l:handler.winid)
+
+ if has_key(a:resp, 'error')
+ call l:handler.requestComplete(0)
+ if has_key(l:handler, 'error')
+ call call(l:handler.error, [a:resp.error.message])
+ else
+ call go#util#EchoError(a:resp.error.message)
+ endif
+ call win_gotoid(l:winid)
+ return
+ endif
+ call l:handler.requestComplete(1)
+
+ let l:winidBeforeHandler = l:handler.winid
+ call call(l:handler.handleResult, [a:resp.result])
+
+ " change the window back to the window that was active when
+ " starting to handle the message _only_ if the handler didn't
+ " update the winid, so that handlers can set the winid if needed
+ " (e.g. :GoDef).
+ if l:handler.winid == l:winidBeforeHandler
+ call win_gotoid(l:winid)
+ endif
+ finally
+ call remove(self.handlers, a:resp.id)
+ endtry
+ endif
+ endfunction
+
+ function! l:lsp.handleNotification(req) dict abort
+ " TODO(bc): handle more notifications (e.g. window/showMessage).
+ if a:req.method == 'textDocument/publishDiagnostics'
+ call self.handleDiagnostics(a:req.params)
+ endif
+ endfunction
+
+ function! l:lsp.handleDiagnostics(data) dict abort
+ let self.diagnosticsQueue = add(self.diagnosticsQueue, a:data)
+ call self.updateDiagnostics()
+ endfunction
+
+ " TODO(bc): process the queue asynchronously
+ function! l:lsp.updateDiagnostics() dict abort
+ for l:data in self.diagnosticsQueue
+ call remove(self.diagnosticsQueue, 0)
+
+ try
+ let l:diagnostics = []
+ let l:errorMatches = []
+ let l:warningMatches = []
+ let l:fname = go#path#FromURI(l:data.uri)
+
+ " get the buffer name relative to the current directory, because
+ " Vim says that a buffer name can't be an absolute path.
+ let l:bufname = fnamemodify(l:fname, ':.')
+
+ if len(l:data.diagnostics) > 0 && (go#config#DiagnosticsEnabled() || bufnr(l:bufname) == bufnr(''))
+ " make sure the buffer is listed and loaded before calling getbufline() on it
+ if !bufexists(l:bufname)
+ "let l:starttime = reltime()
+ call bufadd(l:bufname)
+ endif
+
+ if !bufloaded(l:bufname)
+ "let l:starttime = reltime()
+ call bufload(l:bufname)
+ endif
+
+ for l:diag in l:data.diagnostics
+ " TODO(bc): cache the raw diagnostics when they're not for the
+ " current buffer so that they can be processed when it is the
+ " current buffer and highlight the areas of concern.
+ let [l:error, l:matchpos] = s:errorFromDiagnostic(l:diag, l:bufname, l:fname)
+ let l:diagnostics = add(l:diagnostics, l:error)
+
+ if empty(l:matchpos)
+ continue
+ endif
+
+ if l:diag.severity == 1
+ let l:errorMatches = add(l:errorMatches, l:matchpos)
+ elseif l:diag.severity == 2
+ let l:warningMatches = add(l:warningMatches, l:matchpos)
+ endif
+ endfor
+ endif
+
+ if bufnr(l:bufname) == bufnr('')
+ " only apply highlighting when the diagnostics are for the current
+ " version.
+ let l:lsp = s:lspfactory.get()
+ let l:version = get(l:lsp.fileVersions, l:fname, 0)
+ " it's tempting to only highlight matches when they are for the
+ " current version of the buffer, but that causes problems when the
+ " version number has been updated and the content has not. In such a
+ " case, the diagnostics may not be sent for later versions.
+ call s:highlightMatches(l:errorMatches, l:warningMatches)
+ endif
+
+ let self.diagnostics[l:fname] = l:diagnostics
+ if has_key(self.notificationQueue, l:fname) && len(self.notificationQueue[l:fname]) > 0
+ call call(self.notificationQueue[l:fname][0], copy(l:diagnostics))
+ call remove(self.notificationQueue[l:fname], 0)
+ endif
+ catch
+ call go#util#EchoError(printf('%s: %s', v:throwpoint, v:exception))
+ endtry
+ endfor
+ endfunction
+
function! l:lsp.handleInitializeResult(result) dict abort
+ if go#config#EchoCommandInfo()
+ call go#util#EchoProgress("initialized gopls")
+ endif
+ let status = {
+ \ 'desc': '',
+ \ 'type': 'gopls',
+ \ 'state': 'initialized',
+ \ }
+ call go#statusline#Update(self.wd, status)
+
let self.ready = 1
- " TODO(bc): send initialized message to the server?
+ let l:msg = self.newMessage(go#lsp#message#Initialized())
+ call self.write(l:msg)
" send messages queued while waiting for ready.
for l:item in self.queue
@@ -148,12 +315,34 @@ function! s:newlsp() abort
function! l:lsp.sendMessage(data, handler) dict abort
if !self.last_request_id
- " TODO(bc): run a server per module and one per GOPATH? (may need to
- " keep track of servers by rootUri).
- let l:msg = self.newMessage(go#lsp#message#Initialize(getcwd()))
+ let l:wd = go#util#ModuleRoot()
+ if l:wd == -1
+ call go#util#EchoError('could not determine appropriate working directory for gopls')
+ return -1
+ endif
+
+ if l:wd == ''
+ let l:wd = getcwd()
+ endif
+ let self.wd = l:wd
+
+ if go#config#EchoCommandInfo()
+ call go#util#EchoProgress("initializing gopls")
+ endif
+
+ let l:status = {
+ \ 'desc': '',
+ \ 'type': 'gopls',
+ \ 'state': 'initializing',
+ \ }
+ call go#statusline#Update(l:wd, l:status)
+
+ let self.workspaceDirectories = add(self.workspaceDirectories, l:wd)
+ let l:msg = self.newMessage(go#lsp#message#Initialize(l:wd))
let l:state = s:newHandlerState('')
let l:state.handleResult = funcref('self.handleInitializeResult', [], l:self)
+
let self.handlers[l:msg.id] = l:state
call l:state.start()
@@ -180,7 +369,7 @@ function! s:newlsp() abort
let l:msg = {
\ 'method': a:data.method,
\ 'jsonrpc': '2.0',
- \ }
+ \ }
if !a:data.notification
let self.last_request_id += 1
@@ -194,14 +383,26 @@ function! s:newlsp() abort
return l:msg
endfunction
- function! l:lsp.write(msg) dict abort
- let l:body = json_encode(a:msg)
- let l:data = 'Content-Length: ' . strlen(l:body) . "\r\n\r\n" . l:body
+ function l:lsp.newResponse(id, result) dict abort
+ let l:msg = {
+ \ 'jsonrpc': '2.0',
+ \ 'id': a:id,
+ \ 'result': a:result,
+ \ }
- if go#util#HasDebug('lsp')
- let g:go_lsp_log = add(go#config#LspLog(), "->\n" . l:data)
+ return l:msg
+ endfunction
+
+ function! l:lsp.write(msg) dict abort
+ if empty(get(self, 'job', {}))
+ return
endif
+ let l:body = json_encode(a:msg)
+ let l:data = 'Content-Length: ' . strlen(l:body) . "\r\n\r\n" . l:body
+
+ call s:debug('sent', l:data)
+
if has('nvim')
call chansend(self.job, l:data)
return
@@ -210,19 +411,39 @@ function! s:newlsp() abort
call ch_sendraw(self.job, l:data)
endfunction
- function! l:lsp.exit_cb(job, exit_status) dict abort
+ function! l:lsp.exit_cb(job, exit_status) dict
+ let self.exited = 1
+ if !get(self, 'restarting', 0)
+ return
+ endif
+
+ let l:queue = self.queue
+
+ let l:workspaces = self.workspaceDirectories
+
call s:lspfactory.reset()
+ let l:lsp = s:lspfactory.get()
+
+ " restore workspaces
+ call call('go#lsp#AddWorkspaceDirectory', l:workspaces)
+ " * send DidOpen messages for all buffers that have b:did_lsp_open set
+ " TODO(bc): check modifiable and filetype, too?
+ bufdo if get(b:, 'go_lsp_did_open', 0) | if &modified | call go#lsp#DidOpen(expand('%:p')) | else | call go#lsp#DidChange(expand('%:p')) | endif | endif
+ let l:lsp.queue = extend(l:lsp.queue, l:queue)
+ return
endfunction
- " explicitly bind close_cb to state so that within it, self will always refer
function! l:lsp.close_cb(ch) dict abort
- " TODO(bc): does anything need to be done here?
+ " TODO(bc): remove the buffer variables that indicate that gopls has been
+ " informed that the file is open
endfunction
function! l:lsp.err_cb(ch, msg) dict abort
- if go#util#HasDebug('lsp')
- let g:go_lsp_log = add(go#config#LspLog(), "<-stderr\n" . a:msg)
+ if a:msg =~ '^\d\{4}/\d\d/\d\d\ \d\d:\d\d:\d\d debug server listening on port \d\+$' && !get(self, 'debugport', 0)
+ let self.debugport = substitute(a:msg, '\d\{4}/\d\d/\d\d\ \d\d:\d\d:\d\d debug server listening on port \(\d\+\).*$', '\1', '')
endif
+
+ call s:debug('stderr', a:msg)
endfunction
" explicitly bind callbacks to l:lsp so that within it, self will always refer
@@ -241,14 +462,31 @@ function! s:newlsp() abort
let l:bin_path = go#path#CheckBinPath("gopls")
if empty(l:bin_path)
- return
+ return l:lsp
endif
- " TODO(bc): output a message indicating which directory lsp is going to
- " start in.
- let l:lsp.job = go#job#Start([l:bin_path], l:opts)
+ let l:cmd = [l:bin_path]
+ let l:cmdopts = go#config#GoplsOptions()
+
+ if go#util#HasDebug('lsp')
+ " debugging can be enabled either with g:go_debug or with
+ " g:go_gopls_options; use g:go_gopls_options if it's given in case users
+ " are running the gopls debug server on a known port.
+ let l:needsDebug = 1
+
+ for l:item in l:cmdopts
+ let l:idx = stridx(l:item, '-debug')
+ if l:idx == 0 || l:idx == 1
+ let l:needsDebug = 0
+ endif
+ endfor
+ if l:needsDebug
+ let l:cmd = extend(l:cmd, ['-debug', 'localhost:0'])
+ endif
+ endif
+
+ let l:lsp.job = go#job#Start(l:cmd+l:cmdopts, l:opts)
- " TODO(bc): send the initialize message now?
return l:lsp
endfunction
@@ -260,6 +498,7 @@ function! s:newHandlerState(statustype) abort
\ 'winid': win_getid(winnr()),
\ 'statustype': a:statustype,
\ 'jobdir': getcwd(),
+ \ 'handleResult': funcref('s:noop'),
\ }
" explicitly bind requestComplete to state so that within it, self will
@@ -308,16 +547,17 @@ function! s:requestComplete(ok) abort dict
endfunction
function! s:start() abort dict
- if self.statustype != ''
- let status = {
- \ 'desc': 'current status',
- \ 'type': self.statustype,
- \ 'state': "started",
- \ }
-
- call go#statusline#Update(self.jobdir, status)
- endif
let self.started_at = reltime()
+ if self.statustype == ''
+ return
+ endif
+ let status = {
+ \ 'desc': 'current status',
+ \ 'type': self.statustype,
+ \ 'state': "started",
+ \ }
+
+ call go#statusline#Update(self.jobdir, status)
endfunction
" go#lsp#Definition calls gopls to get the definition of the identifier at
@@ -332,13 +572,17 @@ function! go#lsp#Definition(fname, line, col, handler) abort
let l:state = s:newHandlerState('definition')
let l:state.handleResult = funcref('s:definitionHandler', [function(a:handler, [], l:state)], l:state)
let l:msg = go#lsp#message#Definition(fnamemodify(a:fname, ':p'), a:line, a:col)
- call l:lsp.sendMessage(l:msg, l:state)
+ return l:lsp.sendMessage(l:msg, l:state)
endfunction
function! s:definitionHandler(next, msg) abort dict
+ if a:msg is v:null || len(a:msg) == 0
+ return
+ endif
+
" gopls returns a []Location; just take the first one.
let l:msg = a:msg[0]
- let l:args = [[printf('%s:%d:%d: %s', go#path#FromURI(l:msg.uri), l:msg.range.start.line+1, l:msg.range.start.character+1, 'lsp does not supply a description')]]
+ let l:args = [[printf('%s:%d:%d: %s', go#path#FromURI(l:msg.uri), l:msg.range.start.line+1, go#lsp#lsp#PositionOf(getline(l:msg.range.start.line+1), l:msg.range.start.character), 'lsp does not supply a description')]]
call call(a:next, l:args)
endfunction
@@ -354,13 +598,17 @@ function! go#lsp#TypeDef(fname, line, col, handler) abort
let l:state = s:newHandlerState('type definition')
let l:msg = go#lsp#message#TypeDefinition(fnamemodify(a:fname, ':p'), a:line, a:col)
let l:state.handleResult = funcref('s:typeDefinitionHandler', [function(a:handler, [], l:state)], l:state)
- call l:lsp.sendMessage(l:msg, l:state)
+ return l:lsp.sendMessage(l:msg, l:state)
endfunction
function! s:typeDefinitionHandler(next, msg) abort dict
+ if a:msg is v:null || len(a:msg) == 0
+ return
+ endif
+
" gopls returns a []Location; just take the first one.
let l:msg = a:msg[0]
- let l:args = [[printf('%s:%d:%d: %s', go#path#FromURI(l:msg.uri), l:msg.range.start.line+1, l:msg.range.start.character+1, 'lsp does not supply a description')]]
+ let l:args = [[printf('%s:%d:%d: %s', go#path#FromURI(l:msg.uri), l:msg.range.start.line+1, go#lsp#lsp#PositionOf(getline(l:msg.range.start.line+1), l:msg.range.start.character), 'lsp does not supply a description')]]
call call(a:next, l:args)
endfunction
@@ -369,17 +617,28 @@ function! go#lsp#DidOpen(fname) abort
return
endif
- if !filereadable(a:fname)
+ let l:fname = fnamemodify(a:fname, ':p')
+ if !isdirectory(fnamemodify(l:fname, ':h'))
return
endif
let l:lsp = s:lspfactory.get()
- let l:msg = go#lsp#message#DidOpen(fnamemodify(a:fname, ':p'), join(go#util#GetLines(), "\n") . "\n")
- let l:state = s:newHandlerState('')
- let l:state.handleResult = funcref('s:noop')
- call l:lsp.sendMessage(l:msg, l:state)
+ if !has_key(l:lsp.notificationQueue, l:fname)
+ let l:lsp.notificationQueue[l:fname] = []
+ endif
+
+ let l:lsp.fileVersions[l:fname] = getbufvar(l:fname, 'changedtick')
+
+ let l:msg = go#lsp#message#DidOpen(l:fname, join(go#util#GetLines(), "\n") . "\n", l:lsp.fileVersions[l:fname])
+ let l:state = s:newHandlerState('')
+
+ " TODO(bc): setting a buffer level variable here assumes that a:fname is the
+ " current buffer. Change to a:fname first before setting it and then change
+ " back to active buffer.
let b:go_lsp_did_open = 1
+
+ return l:lsp.sendMessage(l:msg, l:state)
endfunction
function! go#lsp#DidChange(fname) abort
@@ -390,21 +649,29 @@ function! go#lsp#DidChange(fname) abort
return
endif
- call go#lsp#DidOpen(a:fname)
-
- if !filereadable(a:fname)
+ let l:fname = fnamemodify(a:fname, ':p')
+ if !isdirectory(fnamemodify(l:fname, ':h'))
return
endif
+ call go#lsp#DidOpen(a:fname)
+
let l:lsp = s:lspfactory.get()
- let l:msg = go#lsp#message#DidChange(fnamemodify(a:fname, ':p'), join(go#util#GetLines(), "\n") . "\n")
+
+ let l:version = getbufvar(l:fname, 'changedtick')
+ if has_key(l:lsp.fileVersions, l:fname) && l:lsp.fileVersions[l:fname] == l:version
+ return
+ endif
+ let l:lsp.fileVersions[l:fname] = l:version
+
+ let l:msg = go#lsp#message#DidChange(l:fname, join(go#util#GetLines(), "\n") . "\n", l:lsp.fileVersions[l:fname])
let l:state = s:newHandlerState('')
- let l:state.handleResult = funcref('s:noop')
- call l:lsp.sendMessage(l:msg, l:state)
+ return l:lsp.sendMessage(l:msg, l:state)
endfunction
function! go#lsp#DidClose(fname) abort
- if !filereadable(a:fname)
+ let l:fname = fnamemodify(a:fname, ':p')
+ if !isdirectory(fnamemodify(l:fname, ':h'))
return
endif
@@ -413,12 +680,14 @@ function! go#lsp#DidClose(fname) abort
endif
let l:lsp = s:lspfactory.get()
- let l:msg = go#lsp#message#DidClose(fnamemodify(a:fname, ':p'))
+ let l:msg = go#lsp#message#DidClose(l:fname)
let l:state = s:newHandlerState('')
- let l:state.handleResult = funcref('s:noop')
- call l:lsp.sendMessage(l:msg, l:state)
-
+ " TODO(bc): setting a buffer level variable here assumes that a:fname is the
+ " current buffer. Change to a:fname first before setting it and then change
+ " back to active buffer.
let b:go_lsp_did_open = 0
+
+ return l:lsp.sendMessage(l:msg, l:state)
endfunction
function! go#lsp#Completion(fname, line, col, handler) abort
@@ -429,30 +698,199 @@ function! go#lsp#Completion(fname, line, col, handler) abort
let l:state = s:newHandlerState('completion')
let l:state.handleResult = funcref('s:completionHandler', [function(a:handler, [], l:state)], l:state)
let l:state.error = funcref('s:completionErrorHandler', [function(a:handler, [], l:state)], l:state)
- call l:lsp.sendMessage(l:msg, l:state)
+ return l:lsp.sendMessage(l:msg, l:state)
endfunction
function! s:completionHandler(next, msg) abort dict
" gopls returns a CompletionList.
let l:matches = []
+ let l:start = -1
+
for l:item in a:msg.items
- let l:match = {'abbr': l:item.label, 'word': l:item.textEdit.newText, 'info': '', 'kind': go#lsp#completionitemkind#Vim(l:item.kind)}
+ let l:start = l:item.textEdit.range.start.character
+
+ let l:match = {'abbr': l:item.label, 'word': l:item.textEdit.newText, 'info': '', 'kind': go#lsp#completionitemkind#Vim(l:item.kind), 'user_data': '', 'icase': go#config#CodeCompletionIcase()}
if has_key(l:item, 'detail')
- let l:item.info = l:item.detail
+ let l:match.menu = l:item.detail
+ if go#lsp#completionitemkind#IsFunction(l:item.kind) || go#lsp#completionitemkind#IsMethod(l:item.kind)
+ let l:match.info = printf('%s %s', l:item.label, l:item.detail)
+
+ " The detail provided by gopls hasn't always provided the the full
+ " signature including the return value. The label used to be the
+ " function signature and the detail was the return value. Handle
+ " that case for backward compatibility. This can be removed in the
+ " future once it's likely that the majority of users are on a recent
+ " version of gopls.
+ if l:item.detail !~ '^func'
+ let l:match.info = printf('func %s %s', l:item.label, l:item.detail)
+ endif
+ endif
endif
+ let l:match.user_data = l:match.info
if has_key(l:item, 'documentation')
let l:match.info .= "\n\n" . l:item.documentation
endif
let l:matches = add(l:matches, l:match)
endfor
- let l:args = [l:matches]
+ let l:args = [l:start, l:matches]
call call(a:next, l:args)
endfunction
function! s:completionErrorHandler(next, error) abort dict
- call call(a:next, [[]])
+ call call(a:next, [-1, []])
+endfunction
+
+" go#lsp#SameIDs calls gopls to get the references to the identifier at line
+" and col in fname. handler should be a dictionary function that takes a list
+" of strings in the form 'file:line:col: message'. handler will be attached to
+" a dictionary that manages state (statuslines, sets the winid, etc.). handler
+" should take three arguments: an exit_code, a JSON object encoded to a string
+" that mimics guru's ouput for `what`, and third mode parameter that only
+" exists for compatibility with the guru implementation of SameIDs.
+" TODO(bc): refactor to not need the guru adapter.
+function! go#lsp#SameIDs(showstatus, fname, line, col, handler) abort
+ call go#lsp#DidChange(a:fname)
+
+ let l:lsp = s:lspfactory.get()
+ let l:msg = go#lsp#message#References(a:fname, a:line, a:col)
+
+ if a:showstatus
+ let l:state = s:newHandlerState('same ids')
+ else
+ let l:state = s:newHandlerState('')
+ endif
+
+ let l:state.handleResult = funcref('s:sameIDsHandler', [function(a:handler, [], l:state)], l:state)
+ let l:state.error = funcref('s:noop')
+ return l:lsp.sendMessage(l:msg, l:state)
+endfunction
+
+function! s:sameIDsHandler(next, msg) abort dict
+ let l:furi = go#path#ToURI(expand('%:p'))
+
+ let l:result = {
+ \ 'sameids': [],
+ \ 'enclosing': [],
+ \ }
+
+ let l:msg = a:msg
+ if a:msg is v:null
+ let l:msg = []
+ endif
+
+ for l:loc in l:msg
+ if l:loc.uri !=# l:furi
+ continue
+ endif
+
+ if len(l:result.enclosing) == 0
+ let l:result.enclosing = [{
+ \ 'desc': 'identifier',
+ \ 'start': l:loc.range.start.character+1,
+ \ 'end': l:loc.range.end.character+1,
+ \ }]
+ endif
+
+ let l:result.sameids = add(l:result.sameids, printf('%s:%s:%s', go#path#FromURI(l:loc.uri), l:loc.range.start.line+1, l:loc.range.start.character+1))
+ endfor
+
+ call call(a:next, [0, json_encode(l:result), ''])
+endfunction
+
+" go#lsp#Referrers calls gopls to get the references to the identifier at line
+" and col in fname. handler should be a dictionary function that takes a list
+" of strings in the form 'file:line:col: message'. handler will be attached to
+" a dictionary that manages state (statuslines, sets the winid, etc.). handler
+" should take three arguments: an exit_code, a JSON object encoded to a string
+" that mimics guru's ouput for `what`, and third mode parameter that only
+" exists for compatibility with the guru implementation of SameIDs.
+" TODO(bc): refactor to not need the guru adapter.
+function! go#lsp#Referrers(fname, line, col, handler) abort
+ call go#lsp#DidChange(a:fname)
+
+ let l:lsp = s:lspfactory.get()
+ let l:msg = go#lsp#message#References(a:fname, a:line, a:col)
+
+ let l:state = s:newHandlerState('referrers')
+
+ let l:state.handleResult = funcref('s:handleReferences', [function(a:handler, [], l:state)], l:state)
+ let l:state.error = funcref('s:noop')
+ return l:lsp.sendMessage(l:msg, l:state)
+endfunction
+
+function! s:handleReferences(next, msg) abort dict
+ call s:handleLocations(a:next, a:msg)
+endfunction
+
+function! s:handleLocations(next, msg) abort
+ let l:result = []
+
+ let l:msg = a:msg
+
+ if l:msg is v:null
+ let l:msg = []
+ endif
+
+ call sort(l:msg, funcref('s:compareLocations'))
+
+ for l:loc in l:msg
+ let l:fname = go#path#FromURI(l:loc.uri)
+ let l:line = l:loc.range.start.line+1
+ let l:bufnr = bufnr(l:fname)
+ let l:bufinfo = getbufinfo(l:fname)
+
+ try
+ if l:bufnr == -1 || len(l:bufinfo) == 0 || l:bufinfo[0].loaded == 0
+ let l:filecontents = readfile(l:fname, '', l:line)
+ else
+ let l:filecontents = getbufline(l:fname, l:line)
+ endif
+
+ if len(l:filecontents) == 0
+ continue
+ endif
+
+ let l:content = l:filecontents[-1]
+ catch
+ call go#util#EchoError(printf('%s (line %s): %s at %s', l:fname, l:line, v:exception, v:throwpoint))
+ endtry
+
+ let l:item = printf('%s:%s:%s: %s', go#path#FromURI(l:loc.uri), l:line, go#lsp#lsp#PositionOf(l:content, l:loc.range.start.character), l:content)
+
+ let l:result = add(l:result, l:item)
+ endfor
+
+ call call(a:next, [0, l:result, ''])
+endfunction
+
+" go#lsp#Implementations calls gopls to get the implementations to the
+" identifier at line and col in fname. handler should be a dictionary function
+" that takes a list of strings in the form 'file:line:col: message'. handler
+" will be attached to a dictionary that manages state (statuslines, sets the
+" winid, etc.). handler should take three arguments: an exit_code, a JSON
+" object encoded to a string that mimics guru's ouput for guru implements, and
+" a third parameter that only exists for compatibility with guru implements.
+function! go#lsp#Implements(fname, line, col, handler) abort
+ call go#lsp#DidChange(a:fname)
+
+ let l:lsp = s:lspfactory.get()
+ let l:msg = go#lsp#message#Implementation(a:fname, a:line, a:col)
+
+ let l:state = s:newHandlerState('implements')
+
+ let l:state.handleResult = funcref('s:handleImplements', [function(a:handler, [], l:state)], l:state)
+ let l:state.error = funcref('s:handleImplementsError', [function(a:handler, [], l:state)], l:state)
+ return l:lsp.sendMessage(l:msg, l:state)
+endfunction
+
+function! s:handleImplements(next, msg) abort dict
+ call s:handleLocations(a:next, a:msg)
+endfunction
+
+function! s:handleImplementsError(next, error) abort dict
+ call call(a:next, [1, [a:error], ''])
endfunction
function! go#lsp#Hover(fname, line, col, handler) abort
@@ -463,24 +901,89 @@ function! go#lsp#Hover(fname, line, col, handler) abort
let l:state = s:newHandlerState('')
let l:state.handleResult = funcref('s:hoverHandler', [function(a:handler, [], l:state)], l:state)
let l:state.error = funcref('s:noop')
- call l:lsp.sendMessage(l:msg, l:state)
+ return l:lsp.sendMessage(l:msg, l:state)
endfunction
function! s:hoverHandler(next, msg) abort dict
- let l:content = split(a:msg.contents.value, '; ')
- if len(l:content) > 1
- let l:curly = stridx(l:content[0], '{')
- let l:content = extend([l:content[0][0:l:curly]], map(extend([l:content[0][l:curly+1:]], l:content[1:]), '"\t" . v:val'))
- let l:content[len(l:content)-1] = '}'
+ if a:msg is v:null || !has_key(a:msg, 'contents')
+ return
endif
- let l:args = [l:content]
- call call(a:next, l:args)
+ try
+ let l:value = json_decode(a:msg.contents.value)
+ let l:args = [l:value.signature]
+ call call(a:next, l:args)
+ catch
+ " TODO(bc): log the message and/or show an error message.
+ endtry
+endfunction
+
+function! go#lsp#Doc() abort
+ let l:fname = expand('%:p')
+ let [l:line, l:col] = go#lsp#lsp#Position()
+
+ call go#lsp#DidChange(l:fname)
+
+ let l:lsp = s:lspfactory.get()
+ let l:msg = go#lsp#message#Hover(l:fname, l:line, l:col)
+ let l:state = s:newHandlerState('doc')
+ let l:resultHandler = go#promise#New(function('s:docFromHoverResult', [], l:state), 10000, '')
+ let l:state.handleResult = l:resultHandler.wrapper
+ let l:state.error = l:resultHandler.wrapper
+ call l:lsp.sendMessage(l:msg, l:state)
+ return l:resultHandler.await()
+endfunction
+
+function! s:docFromHoverResult(msg) abort dict
+ if type(a:msg) is type('')
+ return [a:msg, 1]
+ endif
+
+ if a:msg is v:null || !has_key(a:msg, 'contents')
+ return ['Undocumented', 0]
+ endif
+
+ let l:value = json_decode(a:msg.contents.value)
+ let l:doc = l:value.fullDocumentation
+ if len(l:doc) is 0
+ let l:doc = 'Undocumented'
+ endif
+ let l:content = printf("%s\n\n%s", l:value.signature, l:doc)
+ return [l:content, 0]
+endfunction
+
+function! go#lsp#DocLink() abort
+ let l:fname = expand('%:p')
+ let [l:line, l:col] = go#lsp#lsp#Position()
+
+ call go#lsp#DidChange(l:fname)
+
+ let l:lsp = s:lspfactory.get()
+ let l:msg = go#lsp#message#Hover(l:fname, l:line, l:col)
+ let l:state = s:newHandlerState('doc url')
+ let l:resultHandler = go#promise#New(function('s:docLinkFromHoverResult', [], l:state), 10000, '')
+ let l:state.handleResult = l:resultHandler.wrapper
+ let l:state.error = l:resultHandler.wrapper
+ call l:lsp.sendMessage(l:msg, l:state)
+ return l:resultHandler.await()
+endfunction
+
+function! s:docLinkFromHoverResult(msg) abort dict
+ if type(a:msg) is type('')
+ return [a:msg, 1]
+ endif
+
+ if a:msg is v:null || !has_key(a:msg, 'contents')
+ return
+ endif
+
+ let l:doc = json_decode(a:msg.contents.value)
+ return [l:doc.link, '']
endfunction
function! go#lsp#Info(showstatus)
let l:fname = expand('%:p')
- let [l:line, l:col] = getpos('.')[1:2]
+ let [l:line, l:col] = go#lsp#lsp#Position()
call go#lsp#DidChange(l:fname)
@@ -492,19 +995,42 @@ function! go#lsp#Info(showstatus)
let l:state = s:newHandlerState('')
endif
- let l:state.handleResult = funcref('s:infoDefinitionHandler', [function('s:info', []), a:showstatus], l:state)
+ let l:state.handleResult = funcref('s:infoDefinitionHandler', [function('s:info', [1], l:state), a:showstatus], l:state)
+ let l:state.error = funcref('s:noop')
+ let l:msg = go#lsp#message#Definition(l:fname, l:line, l:col)
+ return l:lsp.sendMessage(l:msg, l:state)
+endfunction
+
+function! go#lsp#GetInfo()
+ let l:fname = expand('%:p')
+ let [l:line, l:col] = go#lsp#lsp#Position()
+
+ call go#lsp#DidChange(l:fname)
+
+ let l:lsp = s:lspfactory.get()
+
+ let l:state = s:newHandlerState('')
+
+ let l:info = go#promise#New(function('s:info', [0], l:state), 10000, '')
+
+ let l:state.handleResult = funcref('s:infoDefinitionHandler', [l:info.wrapper, 0], l:state)
let l:state.error = funcref('s:noop')
let l:msg = go#lsp#message#Definition(l:fname, l:line, l:col)
call l:lsp.sendMessage(l:msg, l:state)
+ return l:info.await()
endfunction
function! s:infoDefinitionHandler(next, showstatus, msg) abort dict
" gopls returns a []Location; just take the first one.
+ if a:msg is v:null || len(a:msg) == 0
+ return
+ endif
+
let l:msg = a:msg[0]
let l:fname = go#path#FromURI(l:msg.uri)
- let l:line = l:msg.range.start.line+1
- let l:col = l:msg.range.start.character+1
+ let l:line = l:msg.range.start.line
+ let l:col = l:msg.range.start.character
let l:lsp = s:lspfactory.get()
let l:msg = go#lsp#message#Hover(l:fname, l:line, l:col)
@@ -515,15 +1041,556 @@ function! s:infoDefinitionHandler(next, showstatus, msg) abort dict
let l:state = s:newHandlerState('')
endif
- let l:state.handleResult = funcref('s:hoverHandler', [function('s:info', [], l:state)], l:state)
+ let l:state.handleResult = a:next
let l:state.error = funcref('s:noop')
- call l:lsp.sendMessage(l:msg, l:state)
+ return l:lsp.sendMessage(l:msg, l:state)
endfunction
-function! s:info(content) abort dict
+function! s:info(show, msg) abort dict
+ if a:msg is v:null || !has_key(a:msg, 'contents')
+ return
+ endif
+
+ let l:value = json_decode(a:msg.contents.value)
+ let l:content = [l:value.singleLine]
+ let l:content = s:infoFromHoverContent(l:content)
+
+ if a:show
+ call go#util#ShowInfo(l:content)
+ endif
+
+ return l:content
+endfunction
+
+function! s:infoFromHoverContent(content) abort
+ if len(a:content) < 1
+ return ''
+ endif
+
+ let l:content = a:content[0]
+
" strip off the method set and fields of structs and interfaces.
- let l:content = substitute(a:content[0], '{.*', '', '')
- call go#util#ShowInfo(l:content)
+ if l:content =~# '^\(type \)\?[^ ]\+ \(struct\|interface\)'
+ let l:content = substitute(l:content, '{.*', '', '')
+ endif
+
+ return l:content
+endfunction
+
+function! go#lsp#AddWorkspaceDirectory(...) abort
+ if a:0 == 0
+ return
+ endif
+
+ call go#lsp#CleanWorkspaces()
+
+ let l:workspaces = []
+ for l:dir in a:000
+ let l:dir = fnamemodify(l:dir, ':p')
+ if !isdirectory(l:dir)
+ continue
+ endif
+
+ let l:workspaces = add(l:workspaces, l:dir)
+ endfor
+
+ let l:lsp = s:lspfactory.get()
+ let l:state = s:newHandlerState('')
+ let l:lsp.workspaceDirectories = extend(l:lsp.workspaceDirectories, l:workspaces)
+ let l:msg = go#lsp#message#ChangeWorkspaceFolders(l:workspaces, [])
+ call l:lsp.sendMessage(l:msg, l:state)
+
+ return 0
+endfunction
+
+function! go#lsp#CleanWorkspaces() abort
+ let l:workspaces = []
+
+ let l:lsp = s:lspfactory.get()
+
+ let l:i = 0
+ let l:missing = []
+ for l:dir in l:lsp.workspaceDirectories
+ if !isdirectory(l:dir)
+ let l:missing = add(l:missing, l:dir)
+ call remove(l:lsp.workspaceDirectories, l:i)
+ continue
+ endif
+ let l:i += 1
+ endfor
+
+ if len(l:missing) == 0
+ return 0
+ endif
+
+ let l:state = s:newHandlerState('')
+ let l:msg = go#lsp#message#ChangeWorkspaceFolders([], l:missing)
+ call l:lsp.sendMessage(l:msg, l:state)
+
+ return 0
+endfunction
+
+" go#lsp#ResetWorkspaceDiretories removes and then re-adds all workspace
+" folders to cause gopls to send configuration requests for all of them again.
+" This is useful, for instance, when build tags have been added and gopls
+" needs to use them.
+function! go#lsp#ResetWorkspaceDirectories() abort
+ call go#lsp#CleanWorkspaces()
+
+ let l:lsp = s:lspfactory.get()
+
+ let l:state = s:newHandlerState('')
+ let l:msg = go#lsp#message#ChangeWorkspaceFolders(l:lsp.workspaceDirectories, l:lsp.workspaceDirectories)
+ call l:lsp.sendMessage(l:msg, l:state)
+
+ return 0
+endfunction
+
+function! go#lsp#DebugBrowser() abort
+ let l:lsp = s:lspfactory.get()
+ let l:port = get(l:lsp, 'debugport', 0)
+ if !l:port
+ call go#util#EchoError("gopls was not started with debugging enabled. See :help g:go_debug.")
+ return
+ endif
+
+ call go#util#OpenBrowser(printf('http://localhost:%d', l:port))
+endfunction
+
+function! go#lsp#Exit() abort
+ call s:exit(0)
+endfunction
+
+function! go#lsp#Restart() abort
+ call s:exit(1)
+endfunction
+
+function! s:exit(restart) abort
+ if !go#util#has_job() || len(s:lspfactory) == 0 || !has_key(s:lspfactory, 'current')
+ return
+ endif
+
+ let l:lsp = s:lspfactory.get()
+
+ " reset the factory so that future requests don't use the same instance of
+ " gopls.
+ call s:lspfactory.reset()
+
+ let l:lsp.restarting = a:restart
+
+ let l:state = s:newHandlerState('exit')
+
+ let l:msg = go#lsp#message#Shutdown()
+ let l:retval = l:lsp.sendMessage(l:msg, l:state)
+
+ let l:msg = go#lsp#message#Exit()
+ let l:retval = l:lsp.sendMessage(l:msg, l:state)
+
+ return l:retval
+endfunction
+
+let s:log = []
+function! s:debugasync(timer) abort
+ if !go#util#HasDebug('lsp')
+ let s:log = []
+ return
+ endif
+
+ let l:winid = win_getid()
+
+ let l:name = '__GOLSP_LOG__'
+ let l:log_winid = bufwinid(l:name)
+ if l:log_winid == -1
+ silent keepalt botright 10new
+ silent file `='__GOLSP_LOG__'`
+ setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
+ setlocal filetype=golsplog
+ else
+ call win_gotoid(l:log_winid)
+ endif
+
+ try
+ setlocal modifiable
+ for [l:event, l:data] in s:log
+ call remove(s:log, 0)
+ if getline(1) == ''
+ call setline('$', printf('===== %s =====', l:event))
+ else
+ call append('$', printf('===== %s =====', l:event))
+ endif
+ call append('$', split(l:data, "\r\n"))
+ endfor
+ normal! G
+ setlocal nomodifiable
+ finally
+ call win_gotoid(l:winid)
+ endtry
+endfunction
+
+function! s:debug(event, data) abort
+ let l:shouldStart = len(s:log) > 0
+ let s:log = add(s:log, [a:event, a:data])
+
+ if l:shouldStart
+ call timer_start(10, function('s:debugasync', []))
+ endif
+endfunction
+
+function! s:compareLocations(left, right) abort
+ if a:left.uri < a:right.uri
+ return -1
+ endif
+
+ if a:left.uri == a:right.uri && a:left.range.start.line < a:right.range.start.line
+ return -1
+ endif
+
+ if a:left.uri == a:right.uri && a:left.range.start.line == a:right.range.start.line && a:left.range.start.character < a:right.range.start.character
+ return -1
+ endif
+
+ if a:left.uri == a:right.uri && a:left.range.start.line == a:right.range.start.line && a:left.range.start.character == a:right.range.start.character
+ return 0
+ endif
+
+ return 1
+endfunction
+
+function! go#lsp#Diagnostics(...) abort
+ if a:0 == 0
+ return []
+ endif
+
+ let l:dirsToPackages = {}
+
+ let l:lsp = s:lspfactory.get()
+
+ let l:diagnostics = []
+ for [l:key, l:val] in items(l:lsp.diagnostics)
+ let l:dir = fnamemodify(l:key, ':h')
+
+ if !has_key(l:dirsToPackages, l:dir)
+ let l:pkg = go#package#FromPath(l:dir)
+ let l:dirsToPackages[l:dir] = l:pkg
+ else
+ let l:pkg = l:dirsToPackages[l:dir]
+ endif
+
+ if type(l:pkg) == type(0)
+ continue
+ endif
+
+ for l:arg in a:000
+ if l:arg == l:pkg || l:arg == 'all'
+ let l:diagnostics = extend(l:diagnostics, l:val)
+ endif
+ endfor
+ endfor
+
+ return sort(l:diagnostics)
+endfunction
+
+function! go#lsp#AnalyzeFile(fname) abort
+ let l:fname = fnamemodify(a:fname, ':p')
+ if !isdirectory(fnamemodify(l:fname, ':h'))
+ return []
+ endif
+
+ let l:lsp = s:lspfactory.get()
+
+ let l:lastdiagnostics = get(l:lsp.diagnostics, l:fname, [])
+
+ let l:version = l:lsp.fileVersions[a:fname]
+ if l:version == getbufvar(a:fname, 'changedtick')
+ return l:lastdiagnostics
+ endif
+
+ call go#lsp#DidChange(a:fname)
+
+ let l:diagnostics = go#promise#New(function('s:setDiagnostics', []), 10000, l:lastdiagnostics)
+ let l:lsp.notificationQueue[l:fname] = add(l:lsp.notificationQueue[l:fname], l:diagnostics.wrapper)
+ return l:diagnostics.await()
+endfunction
+
+function! s:setDiagnostics(...) abort
+ return a:000
+endfunction
+
+" s:processDiagnostic converts a diagnostic into an error string. It returns
+" the errors string and the match position described in the diagnostic. The
+" match position will be an empty list when bufname is not a valid name for
+" the current buffer.
+function! s:errorFromDiagnostic(diagnostic, bufname, fname) abort
+ let l:range = a:diagnostic.range
+
+ let l:line = l:range.start.line + 1
+ let l:buflines = getbufline(a:bufname, l:line)
+ let l:col = ''
+ if len(l:buflines) > 0
+ let l:col = go#lsp#lsp#PositionOf(l:buflines[0], l:range.start.character)
+ endif
+ let l:error = printf('%s:%s:%s:%s: %s', a:fname, l:line, l:col, go#lsp#lsp#SeverityToErrorType(a:diagnostic.severity), a:diagnostic.message)
+
+ if !(a:diagnostic.severity == 1 || a:diagnostic.severity == 2)
+ return [l:error, []]
+ endif
+
+ " return when the diagnostic is not for the current buffer.
+ if bufnr(a:bufname) != bufnr('')
+ return [l:error, []]
+ end
+
+ let l:endline = l:range.end.line + 1
+ " don't bother trying to highlight errors or warnings that span
+ " the whole file (e.g when there's missing package documentation).
+ if l:line == 1 && (l:endline) == line('$')
+ return [l:error, []]
+ endif
+ let l:endcol = go#lsp#lsp#PositionOf(getline(l:endline), l:range.end.character)
+
+ " the length of the match is the number of bytes between the start of
+ " the match and the end of the match.
+ let l:matchLength = line2byte(l:endline) + l:endcol - (line2byte(l:line) + l:col)
+ let l:pos = [l:line, l:col, l:matchLength]
+
+ return [l:error, l:pos]
+endfunction
+
+function! s:highlightMatches(errorMatches, warningMatches) abort
+ " set buffer variables for errors and warnings to zero values
+ let b:go_diagnostic_matches = {'errors': [], 'warnings': []}
+
+ if hlexists('goDiagnosticError')
+ " clear the old matches just before adding the new ones to keep flicker
+ " to a minimum.
+ call go#util#ClearHighlights('goDiagnosticError')
+ if go#config#HighlightDiagnosticErrors()
+ let b:go_diagnostic_matches.errors = copy(a:errorMatches)
+ call go#util#HighlightPositions('goDiagnosticError', a:errorMatches)
+ endif
+ endif
+
+ if hlexists('goDiagnosticWarning')
+ " clear the old matches just before adding the new ones to keep flicker
+ " to a minimum.
+ call go#util#ClearHighlights('goDiagnosticWarning')
+ if go#config#HighlightDiagnosticWarnings()
+ let b:go_diagnostic_matches.warnings = copy(a:warningMatches)
+ call go#util#HighlightPositions('goDiagnosticWarning', a:warningMatches)
+ endif
+ endif
+
+ " re-apply matches at the time the buffer is displayed in a new window or
+ " redisplayed in an existing window: e.g. :edit,
+ augroup vim-go-diagnostics
+ autocmd! *
+ autocmd BufDelete autocmd! vim-go-diagnostics *
+ if has('textprop')
+ autocmd BufReadPost nested call s:highlightMatches(b:go_diagnostic_matches.errors, b:go_diagnostic_matches.warnings)
+ else
+ autocmd BufWinEnter nested call s:highlightMatches(b:go_diagnostic_matches.errors, b:go_diagnostic_matches.warnings)
+ endif
+ augroup end
+endfunction
+
+" ClearDiagnosticHighlights removes all goDiagnosticError and
+" goDiagnosticWarning matches.
+function! go#lsp#ClearDiagnosticHighlights() abort
+ call go#util#ClearHighlights('goDiagnosticError')
+ call go#util#ClearHighlights('goDiagnosticWarning')
+endfunction
+
+" Format formats the current buffer.
+function! go#lsp#Format() abort
+ let l:fname = expand('%:p')
+ " send the current file so that TextEdits will be relative to the current
+ " state of the buffer.
+ call go#lsp#DidChange(l:fname)
+
+ let l:lsp = s:lspfactory.get()
+
+ let l:state = s:newHandlerState('')
+ let l:handleFormat = go#promise#New(function('s:handleFormat', [], l:state), 10000, '')
+ let l:state.handleResult = l:handleFormat.wrapper
+ let l:state.error = l:handleFormat.wrapper
+ let l:state.handleError = function('s:handleFormatError', [l:fname], l:state)
+ let l:msg = go#lsp#message#Format(l:fname)
+ call l:lsp.sendMessage(l:msg, l:state)
+
+ call go#fmt#CleanErrors()
+
+ " await the result to avoid any race conditions among autocmds (e.g.
+ " BufWritePre and BufWritePost)
+ call l:handleFormat.await()
+endfunction
+
+" Imports executes the source.organizeImports code action for the current
+" buffer.
+function! go#lsp#Imports() abort
+ let l:fname = expand('%:p')
+ " send the current file so that TextEdits will be relative to the current
+ " state of the buffer.
+ call go#lsp#DidChange(l:fname)
+
+ let l:lsp = s:lspfactory.get()
+
+ let l:state = s:newHandlerState('')
+ let l:handler = go#promise#New(function('s:handleCodeAction', [], l:state), 10000, '')
+ let l:state.handleResult = l:handler.wrapper
+ let l:state.error = l:handler.wrapper
+ let l:state.handleError = function('s:handleCodeActionError', [l:fname], l:state)
+ let l:msg = go#lsp#message#CodeActionImports(l:fname)
+ call l:lsp.sendMessage(l:msg, l:state)
+
+ " await the result to avoid any race conditions among autocmds (e.g.
+ " BufWritePre and BufWritePost)
+ call l:handler.await()
+endfunction
+
+function! s:handleFormat(msg) abort dict
+ call go#fmt#CleanErrors()
+
+ if type(a:msg) is type('')
+ call self.handleError(a:msg)
+ return
+ endif
+ call s:applyTextEdits(a:msg)
+endfunction
+
+function! s:handleCodeAction(msg) abort dict
+ if type(a:msg) is type('')
+ call self.handleError(a:msg)
+ return
+ endif
+
+ if a:msg is v:null
+ return
+ endif
+
+ for l:item in a:msg
+ if get(l:item, 'kind', '') is 'source.organizeImports'
+ if !has_key(l:item, 'edit')
+ continue
+ endif
+ if !has_key(l:item.edit, 'documentChanges')
+ continue
+ endif
+ for l:change in l:item.edit.documentChanges
+ if !has_key(l:change, 'edits')
+ continue
+ endif
+ " TODO(bc): change to the buffer for l:change.textDocument.uri
+ call s:applyTextEdits(l:change.edits)
+ endfor
+ endif
+ endfor
+endfunction
+
+function s:applyTextEdits(msg) abort
+ if a:msg is v:null
+ return
+ endif
+
+ " process the TextEdit list in reverse order, because the positions are
+ " based on the current line numbers; processing in forward order would
+ " require keeping track of how the proper position of each TextEdit would be
+ " affected by all the TextEdits that came before.
+ call reverse(sort(a:msg, function('s:textEditLess')))
+ for l:msg in a:msg
+ let l:startline = l:msg.range.start.line+1
+ let l:endline = l:msg.range.end.line+1
+ let l:text = l:msg.newText
+
+ " handle the deletion of whole lines
+ if len(l:text) == 0 && l:msg.range.start.character == 0 && l:msg.range.end.character == 0 && l:startline < l:endline
+ call s:deleteline(l:startline, l:endline-1)
+ continue
+ endif
+
+ let l:startcontent = getline(l:startline)
+ let l:preSliceEnd = 0
+ if l:msg.range.start.character > 0
+ let l:preSliceEnd = go#lsp#lsp#PositionOf(l:startcontent, l:msg.range.start.character-1) - 1
+ let l:startcontent = l:startcontent[:l:preSliceEnd]
+ elseif l:endline == l:startline && (l:msg.range.end.character == 0 || l:msg.range.start.character == 0)
+ " l:startcontent should be the empty string when l:text is a
+ " replacement at the beginning of the line.
+ let l:startcontent = ''
+ endif
+
+ let l:endcontent = getline(l:endline)
+ let l:postSliceStart = 0
+ if l:msg.range.end.character > 0
+ let l:postSliceStart = go#lsp#lsp#PositionOf(l:endcontent, l:msg.range.end.character-1)
+ let l:endcontent = l:endcontent[(l:postSliceStart):]
+ endif
+
+ " There isn't an easy way to replace the text in a byte or character
+ " range, so append to l:text any text on l:endline starting from
+ " l:postSliceStart and prepend to l:text any text on l:startline prior to
+ " l:preSliceEnd, and finally replace the lines with a delete followed by
+ " and append.
+ let l:text = printf('%s%s%s', l:startcontent, l:text, l:endcontent)
+
+ " TODO(bc): deal with the undo file
+ " TODO(bc): deal with folds
+
+ call s:deleteline(l:startline, l:endline)
+ for l:line in split(l:text, "\n", 1)
+ call append(l:startline-1, l:line)
+ let l:startline += 1
+ endfor
+ endfor
+
+ call go#lsp#DidChange(expand('%:p'))
+ return
+endfunction
+
+function! s:handleFormatError(filename, msg) abort dict
+ if go#config#FmtFailSilently()
+ return
+ endif
+
+ let l:errors = split(a:msg, '\n')
+ let l:errors = map(l:errors, printf('substitute(v:val, ''^'', ''%s:'', '''')', a:filename))
+ let l:errors = join(l:errors, "\n")
+ call go#fmt#ShowErrors(l:errors)
+endfunction
+
+function! s:handleCodeActionError(filename, msg) abort dict
+ " TODO(bc): handle the error?
+endfunction
+
+function! s:textEditLess(left, right) abort
+ " TextEdits in a TextEdit[] never overlap and Vim's sort() is stable.
+ if a:left.range.start.line < a:right.range.start.line
+ return -1
+ endif
+
+ if a:left.range.start.line > a:right.range.start.line
+ return 1
+ endif
+
+ if a:left.range.start.line == a:right.range.start.line
+ if a:left.range.start.character < a:right.range.start.character
+ return -1
+ endif
+
+ if a:left.range.start.character > a:right.range.start.character
+ return 1
+ endif
+ endif
+
+ " return 0, because a:left an a:right refer to the same position.
+ return 0
+endfunction
+
+function! s:deleteline(start, end) abort
+ if exists('*deletebufline')
+ call deletebufline('', a:start, a:end)
+ else
+ call execute(printf('%d,%d d_', a:start, a:end))
+ endif
endfunction
" restore Vi compatibility settings
diff --git a/pack/acp/start/vim-go/autoload/go/lsp/completionitemkind.vim b/pack/acp/start/vim-go/autoload/go/lsp/completionitemkind.vim
index 37c00a8..0202bd2 100644
--- a/pack/acp/start/vim-go/autoload/go/lsp/completionitemkind.vim
+++ b/pack/acp/start/vim-go/autoload/go/lsp/completionitemkind.vim
@@ -28,7 +28,7 @@ let s:Event = 23
let s:Operator = 24
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
return 'f'
elseif a:kind == s:Variable || a:kind == s:Constant
@@ -40,6 +40,22 @@ function! go#lsp#completionitemkind#Vim(kind)
endif
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
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/pack/acp/start/vim-go/autoload/go/lsp/lsp.vim b/pack/acp/start/vim-go/autoload/go/lsp/lsp.vim
new file mode 100644
index 0000000..c0cf3e0
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/lsp/lsp.vim
@@ -0,0 +1,72 @@
+" don't spam the user when Vim is started in Vi compatibility mode
+let s:cpo_save = &cpo
+set cpo&vim
+
+" 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
diff --git a/pack/acp/start/vim-go/autoload/go/lsp/lsp_test.vim b/pack/acp/start/vim-go/autoload/go/lsp/lsp_test.vim
new file mode 100644
index 0000000..24a2a74
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/lsp/lsp_test.vim
@@ -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
diff --git a/pack/acp/start/vim-go/autoload/go/lsp/message.vim b/pack/acp/start/vim-go/autoload/go/lsp/message.vim
index 6398185..90f9f5b 100644
--- a/pack/acp/start/vim-go/autoload/go/lsp/message.vim
+++ b/pack/acp/start/vim-go/autoload/go/lsp/message.vim
@@ -10,17 +10,100 @@ function! go#lsp#message#Initialize(wd) abort
\ 'processId': getpid(),
\ 'rootUri': go#path#ToURI(a:wd),
\ 'capabilities': {
- \ 'workspace': {},
+ \ 'workspace': {
+ \ 'workspaceFolders': v:true,
+ \ 'didChangeConfiguration': {
+ \ 'dynamicRegistration': v:true,
+ \ },
+ \ 'configuration': v:true,
+ \ },
\ 'textDocument': {
\ 'hover': {
\ '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
+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
return {
\ 'notification': 0,
@@ -47,7 +130,20 @@ function! go#lsp#message#TypeDefinition(file, line, col) abort
\ }
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 {
\ 'notification': 1,
\ 'method': 'textDocument/didOpen',
@@ -56,18 +152,20 @@ function! go#lsp#message#DidOpen(file, content) abort
\ 'uri': go#path#ToURI(a:file),
\ 'languageId': 'go',
\ 'text': a:content,
+ \ 'version': a:version,
\ }
\ }
\ }
endfunction
-function! go#lsp#message#DidChange(file, content) abort
+function! go#lsp#message#DidChange(file, content, version) abort
return {
\ 'notification': 1,
\ 'method': 'textDocument/didChange',
\ 'params': {
\ 'textDocument': {
\ 'uri': go#path#ToURI(a:file),
+ \ 'version': a:version,
\ },
\ 'contentChanges': [
\ {
@@ -103,6 +201,22 @@ function! go#lsp#message#Completion(file, line, col) abort
\ }
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
return {
\ 'notification': 0,
@@ -116,8 +230,120 @@ function! go#lsp#message#Hover(file, line, col) abort
\ }
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
- return {'line': a:line - 1, 'character': a:col-1}
+ return {'line': a:line, 'character': a:col}
endfunction
" restore Vi compatibility settings
diff --git a/pack/acp/start/vim-go/autoload/go/lsp_test.vim b/pack/acp/start/vim-go/autoload/go/lsp_test.vim
new file mode 100644
index 0000000..55bf8ce
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/lsp_test.vim
@@ -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
diff --git a/pack/acp/start/vim-go/autoload/go/mod.vim b/pack/acp/start/vim-go/autoload/go/mod.vim
index 021f7f9..a38aabe 100644
--- a/pack/acp/start/vim-go/autoload/go/mod.vim
+++ b/pack/acp/start/vim-go/autoload/go/mod.vim
@@ -7,11 +7,15 @@ let s:go_major_version = ""
function! go#mod#Format() abort
" go mod only exists in `v1.11`
if empty(s:go_major_version)
- let tokens = matchlist(go#util#System("go version"), '\d\+.\(\d\+\)\(\.\d\+\)\? ')
- let s:go_major_version = str2nr(tokens[1])
+ let tokens = matchlist(go#util#Exec(['go', 'version']), '\d\+.\(\d\+\)\(\.\d\+\)\? ')
+ if len(tokens) > 0
+ let s:go_major_version = str2nr(tokens[1])
+ else
+ let s:go_major_version = ""
+ 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")
return
endif
@@ -79,18 +83,11 @@ function! go#mod#update_file(source, target)
let l:listtype = go#list#Type("GoModFmt")
- " the title information was introduced with 7.4-2200
- " https://github.com/vim/vim/commit/d823fa910cca43fec3c31c030ee908a14c272640
- if has('patch-7.4.2200')
- " clean up previous list
- if l:listtype == "quickfix"
- let l:list_title = getqflist({'title': 1})
- else
- let l:list_title = getloclist(0, {'title': 1})
- endif
+ " clean up previous list
+ if l:listtype == "quickfix"
+ let l:list_title = getqflist({'title': 1})
else
- " can't check the title, so assume that the list was for go fmt.
- let l:list_title = {'title': 'Format'}
+ let l:list_title = getloclist(0, {'title': 1})
endif
if has_key(l:list_title, "title") && l:list_title['title'] == "Format"
diff --git a/pack/acp/start/vim-go/autoload/go/package.vim b/pack/acp/start/vim-go/autoload/go/package.vim
index ced39cc..59c7101 100644
--- a/pack/acp/start/vim-go/autoload/go/package.vim
+++ b/pack/acp/start/vim-go/autoload/go/package.vim
@@ -39,7 +39,7 @@ function! s:paths() abort
if executable('go')
let s:goroot = go#util#env("goroot")
if go#util#ShellError() != 0
- echomsg '''go env GOROOT'' failed'
+ call go#util#EchoError('`go env GOROOT` failed')
endif
else
let s:goroot = $GOROOT
@@ -82,6 +82,10 @@ function! s:vendordirs() abort
if l:err != 0
return []
endif
+ if empty(l:root)
+ return []
+ endif
+
let l:root = split(l:root, '\n')[0] . go#util#PathSep() . 'src'
let [l:dir, l:err] = go#util#ExecInDir(['go', 'list', '-f', '{{.Dir}}'])
@@ -111,55 +115,77 @@ function! s:vendordirs() abort
endfunction
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
- let dir = expand("%:p:h")
+ let l:dir = expand("%:p:h")
if has_key(s:import_paths, dir)
- return s:import_paths[dir]
+ return s:import_paths[l:dir]
endif
- let [l:out, l:err] = go#util#ExecInDir(['go', 'list'])
- if l:err != 0
+ let l:importpath = go#package#FromPath(l:dir)
+ if type(l:importpath) == type(0)
return -1
endif
- let l:importpath = split(out, '\n')[0]
-
- " go list returns '_CURRENTDIRECTORY' if the directory is not inside GOPATH.
- " Check it and retun an error if that is the case
- if l:importpath[0] ==# '_'
- return -1
- endif
-
- let s:import_paths[dir] = l:importpath
+ let s:import_paths[l:dir] = l:importpath
return l:importpath
endfunction
-
-" FromPath returns the import path of arg.
+" go#package#FromPath returns the import path of arg. -1 is returned when 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
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
let l:dir = getcwd()
- let l:path = a:arg
+ let l:path = fnamemodify(a:arg, ':p')
if !isdirectory(l:path)
let l:path = fnamemodify(l:path, ':h')
endif
execute l:cd fnameescape(l:path)
- let [l:out, l:err] = go#util#Exec(['go', 'list'])
- execute l:cd fnameescape(l:dir)
- if l:err != 0
- return -1
- endif
+ try
+ if glob("*.go") == ""
+ " There's no Go code in this directory. We might be in a module directory
+ " which doesn't have any code at this level. To avoid `go list` making a
+ " 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.
- " Check it and retun an error if that is the case
+ " go list returns '_CURRENTDIRECTORY' if the directory is in a null module
+ " (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] ==# '_'
- 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
return l:importpath
@@ -206,10 +232,17 @@ function! go#package#Complete(ArgLead, CmdLine, CursorPos) abort
let vendordirs = s:vendordirs()
+ let l:modcache = go#util#env('gomodcache')
+
let ret = {}
for dir in dirs
" this may expand to multiple lines
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 = extend(root, vendordirs)
let root = add(root, module)
@@ -234,9 +267,8 @@ function! go#package#Complete(ArgLead, CmdLine, CursorPos) abort
let glob = module.dir
endif
elseif stridx(module.path, a:ArgLead) == 0 && stridx(module.path, '/', len(a:ArgLead)) < 0
- " use the module directory when a:ArgLead is contained in
- " module.path and module.path does not have any path segments after
- " a:ArgLead.
+ " use the module directory when module.path begins wih a:ArgLead and
+ " module.path does not have any path segments after a:ArgLead.
let glob = module.dir
else
continue
@@ -251,6 +283,11 @@ function! go#package#Complete(ArgLead, CmdLine, CursorPos) abort
if fnamemodify(candidate, ':t') == 'vendor'
continue
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 .= '/'
elseif candidate !~ '\.a$'
continue
diff --git a/pack/acp/start/vim-go/autoload/go/path.vim b/pack/acp/start/vim-go/autoload/go/path.vim
index 656afec..0d81d45 100644
--- a/pack/acp/start/vim-go/autoload/go/path.vim
+++ b/pack/acp/start/vim-go/autoload/go/path.vim
@@ -28,11 +28,11 @@ function! go#path#GoPath(...) abort
let s:initial_go_path = ""
endif
- echon "vim-go: " | echohl Function | echon "GOPATH restored to ". $GOPATH | echohl None
+ call go#util#EchoInfo("GOPATH restored to ". $GOPATH)
return
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 $GOPATH = a:1
endfunction
diff --git a/pack/acp/start/vim-go/autoload/go/play.vim b/pack/acp/start/vim-go/autoload/go/play.vim
index 117af25..980421e 100644
--- a/pack/acp/start/vim-go/autoload/go/play.vim
+++ b/pack/acp/start/vim-go/autoload/go/play.vim
@@ -4,7 +4,7 @@ set cpo&vim
function! go#play#Share(count, line1, line2) abort
if !executable('curl')
- echohl ErrorMsg | echomsg "vim-go: require 'curl' command" | echohl None
+ call go#util#EchoError('cannot share: curl cannot be found')
return
endif
@@ -20,8 +20,7 @@ function! go#play#Share(count, line1, line2) abort
call delete(share_file)
if l:err != 0
- echom 'A error has occurred. Run this command to see what the problem is:'
- echom go#util#Shelljoin(l:cmd)
+ call go#util#EchoError(['A error has occurred. Run this command to see what the problem is:', go#util#Shelljoin(l:cmd)])
return
endif
@@ -38,7 +37,7 @@ function! go#play#Share(count, line1, line2) abort
call go#util#OpenBrowser(url)
endif
- echo "vim-go: snippet uploaded: ".url
+ call go#util#EchoInfo('snippet uploaded: ' . url)
endfunction
diff --git a/pack/acp/start/vim-go/autoload/go/promise.vim b/pack/acp/start/vim-go/autoload/go/promise.vim
new file mode 100644
index 0000000..4782883
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/promise.vim
@@ -0,0 +1,58 @@
+" don't spam the user when Vim is started in Vi compatibility mode
+let s:cpo_save = &cpo
+set cpo&vim
+
+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
diff --git a/pack/acp/start/vim-go/autoload/go/promise_test.vim b/pack/acp/start/vim-go/autoload/go/promise_test.vim
new file mode 100644
index 0000000..f4473fe
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/promise_test.vim
@@ -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
diff --git a/pack/acp/start/vim-go/autoload/go/referrers.vim b/pack/acp/start/vim-go/autoload/go/referrers.vim
new file mode 100644
index 0000000..223fec6
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/referrers.vim
@@ -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
diff --git a/pack/acp/start/vim-go/autoload/go/rename.vim b/pack/acp/start/vim-go/autoload/go/rename.vim
index 30ff590..f5d167f 100644
--- a/pack/acp/start/vim-go/autoload/go/rename.vim
+++ b/pack/acp/start/vim-go/autoload/go/rename.vim
@@ -20,8 +20,10 @@ function! go#rename#Rename(bang, ...) abort
let to_identifier = a:1
endif
+ let l:bin = go#config#RenameCommand()
+
" 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)
return
endif
@@ -29,7 +31,18 @@ function! go#rename#Rename(bang, ...) abort
let fname = expand('%:p')
let pos = go#util#OffsetCursor()
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()
call s:rename_job({
@@ -39,7 +52,12 @@ function! go#rename#Rename(bang, ...) abort
return
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'))
endfunction
@@ -54,6 +72,11 @@ function s:rename_job(args)
call go#cmd#autowrite()
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.
let l:cbs.exit_cb = funcref('s:exit_cb', [l:cbs.exit_cb])
diff --git a/pack/acp/start/vim-go/autoload/go/statusline.vim b/pack/acp/start/vim-go/autoload/go/statusline.vim
index f0ec5cd..9779d76 100644
--- a/pack/acp/start/vim-go/autoload/go/statusline.vim
+++ b/pack/acp/start/vim-go/autoload/go/statusline.vim
@@ -27,9 +27,8 @@ let s:last_status = ""
" if not it returns an empty string. This function should be plugged directly
" into the statusline.
function! go#statusline#Show() abort
- " lazy initialiation of the cleaner
+ " lazy initialization of the cleaner
if !s:timer_id
- " clean every 60 seconds all statuses
let interval = go#config#StatuslineDuration()
let s:timer_id = timer_start(interval, function('go#statusline#Clear'), {'repeat': -1})
endif
@@ -57,9 +56,9 @@ function! go#statusline#Show() abort
" only update highlight if status has changed.
if status_text != s:last_status
- if status.state =~ "success" || status.state =~ "finished" || status.state =~ "pass"
+ 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
- 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
elseif status.state =~ "failed"
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
" up. Otherwise every job will reset the timer when this function is called
" 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.
- " 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)
let s:timer_id = 0
endfunction
@@ -94,6 +94,10 @@ endfunction
" 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.
function! go#statusline#Clear(timer_id) abort
+ call s:clear()
+endfunction
+
+function! s:clear()
for [status_dir, status] in items(s:statuses)
let elapsed_time = reltimestr(reltime(status.created_at))
" strip whitespace
diff --git a/pack/acp/start/vim-go/autoload/go/tags.vim b/pack/acp/start/vim-go/autoload/go/tags.vim
index c1239b0..2feeaa5 100644
--- a/pack/acp/start/vim-go/autoload/go/tags.vim
+++ b/pack/acp/start/vim-go/autoload/go/tags.vim
@@ -98,7 +98,7 @@ func s:write_out(out) abort
if has_key(result, 'errors')
let l:winnr = winnr()
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']))
"prevent jumping to quickfix list
@@ -124,6 +124,7 @@ func s:create_cmd(args) abort
let l:mode = a:args.mode
let l:cmd_args = a:args.cmd_args
let l:modifytags_transform = go#config#AddtagsTransform()
+ let l:modifytags_skip_unexported = go#config#AddtagsSkipUnexported()
" start constructing the command
let cmd = [bin_path]
@@ -131,6 +132,10 @@ func s:create_cmd(args) abort
call extend(cmd, ["-file", a:args.fname])
call extend(cmd, ["-transform", l:modifytags_transform])
+ if l:modifytags_skip_unexported
+ call extend(cmd, ["-skip-unexported"])
+ endif
+
if has_key(a:args, "modified")
call add(cmd, "-modified")
endif
diff --git a/pack/acp/start/vim-go/autoload/go/template.vim b/pack/acp/start/vim-go/autoload/go/template.vim
index 3f5e538..404047a 100644
--- a/pack/acp/start/vim-go/autoload/go/template.vim
+++ b/pack/acp/start/vim-go/autoload/go/template.vim
@@ -24,8 +24,13 @@ function! go#template#create() abort
else
let l:template_file = go#config#TemplateFile()
endif
- let l:template_path = go#util#Join(l:root_dir, "templates", l:template_file)
- silent exe 'keepalt 0r ' . fnameescape(l:template_path)
+ " If template_file is an absolute path, use it as-is. This is to support
+ " 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
else
let l:content = printf("package %s", l:package_name)
diff --git a/pack/acp/start/vim-go/autoload/go/term.vim b/pack/acp/start/vim-go/autoload/go/term.vim
index d7a463e..dca9b50 100644
--- a/pack/acp/start/vim-go/autoload/go/term.vim
+++ b/pack/acp/start/vim-go/autoload/go/term.vim
@@ -2,6 +2,8 @@
let s:cpo_save = &cpo
set cpo&vim
+let s:bufnameprefix = 'goterm://'
+
" new creates a new terminal with the given command. Mode is set based on the
" global variable g:go_term_mode, which is by default set to :vsplit
function! go#term#new(bang, cmd, errorformat) abort
@@ -15,6 +17,10 @@ function! go#term#newmode(bang, cmd, errorformat, mode) abort
let l:mode = go#config#TermMode()
endif
+ if go#config#TermReuse()
+ call s:closeterm()
+ endif
+
let l:state = {
\ 'cmd': a:cmd,
\ 'bang' : a:bang,
@@ -24,55 +30,96 @@ function! go#term#newmode(bang, cmd, errorformat, mode) abort
\ 'errorformat': a:errorformat,
\ }
- " execute go build in the files directory
- let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
- let l:dir = getcwd()
-
- execute l:cd . fnameescape(expand("%:p:h"))
+ " execute the command in the current file's directory
+ let l:dir = go#util#Chdir(expand('%:p:h'))
execute l:mode . ' __go_term__'
-
setlocal filetype=goterm
setlocal bufhidden=delete
setlocal winfixheight
+ " TODO(bc)?: setlocal winfixwidth
setlocal noswapfile
setlocal nobuflisted
- " explicitly bind callbacks to state so that within them, self will always
- " refer to state. See :help Partial for more information.
- "
- " Don't set an on_stderr, because it will be passed the same data as
- " on_stdout. See https://github.com/neovim/neovim/issues/2836
- let l:job = {
- \ 'on_stdout': function('s:on_stdout', [], state),
- \ 'on_exit' : function('s:on_exit', [], state),
- \ }
+ " setup job for nvim
+ if has('nvim')
+ " explicitly bind callbacks to state so that within them, self will always
+ " refer to state. See :help Partial for more information.
+ "
+ " Don't set an on_stderr, because it will be passed the same data as
+ " on_stdout. See https://github.com/neovim/neovim/issues/2836
+ let 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)
- let l:state.termwinid = win_getid(winnr())
+ " resize new term if needed.
+ 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.
- let l:height = go#config#TermHeight()
- let l:width = go#config#TermWidth()
+ " setup term for vim8
+ elseif has('terminal')
+ " 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
- " 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
+ let l:term = {
+ \ 'out_cb': function('s:out_cb', [], state),
+ \ 'exit_cb' : function('s:exit_cb', [], state),
+ \ 'curwin': 1,
+ \ 'term_name': l:termname,
+ \ }
+
+ 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
- " 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)
-
return l:state.id
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
" 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
@@ -95,9 +142,21 @@ function! s:on_stdout(job_id, data, event) dict abort
endif
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
+ 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())
- call win_gotoid(self.winid)
+ call win_gotoid(a:state.winid)
+
let l:listtype = go#list#Type("_term")
if a:exit_status == 0
@@ -106,40 +165,106 @@ function! s:on_exit(job_id, exit_status, event) dict abort
return
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
- let l:title = join(self.cmd)
+ let l:title = join(a:state.cmd)
endif
let l:i = 0
- while l:i < len(self.stdout)
- let self.stdout[l:i] = substitute(self.stdout[l:i], "\r$", '', 'g')
+ while l:i < len(a:state.stdout)
+ let a:state.stdout[l:i] = substitute(a:state.stdout[l:i], "\r$", '', 'g')
let l:i += 1
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)
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)
call go#util#EchoError( '[' . l:title . '] ' . "FAIL")
+ call go#util#Chdir(l:dir)
call win_gotoid(l:winid)
return
endif
- " close terminal; we don't need it anymore
- call win_gotoid(self.termwinid)
- close!
-
- if self.bang
+ if a:state.bang
+ call go#util#Chdir(l:dir)
call win_gotoid(l:winid)
return
endif
- call win_gotoid(self.winid)
+ call win_gotoid(a:state.winid)
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
" restore Vi compatibility settings
diff --git a/pack/acp/start/vim-go/autoload/go/term_test.vim b/pack/acp/start/vim-go/autoload/go/term_test.vim
index 40f0a52..55e2e10 100644
--- a/pack/acp/start/vim-go/autoload/go/term_test.vim
+++ b/pack/acp/start/vim-go/autoload/go/term_test.vim
@@ -3,7 +3,7 @@ let s:cpo_save = &cpo
set cpo&vim
func! Test_GoTermNewMode()
- if !has('nvim')
+ if !(has('nvim') || has('terminal'))
return
endif
@@ -22,12 +22,13 @@ func! Test_GoTermNewMode()
call assert_equal(actual, l:expected)
finally
+ sleep 50m
call delete(l:tmp, 'rf')
endtry
endfunc
func! Test_GoTermNewMode_SplitRight()
- if !has('nvim')
+ if !(has('nvim') || has('terminal'))
return
endif
@@ -46,11 +47,46 @@ func! Test_GoTermNewMode_SplitRight()
call assert_equal(actual, l:expected)
finally
+ sleep 50m
call delete(l:tmp, 'rf')
set nosplitright
endtry
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
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/config/buildtags/buildtags.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/config/buildtags/buildtags.go
new file mode 100644
index 0000000..5bf7a88
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/config/buildtags/buildtags.go
@@ -0,0 +1,5 @@
+package config
+
+func Example() {
+ foo()
+}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/config/buildtags/constrainedfoo.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/config/buildtags/constrainedfoo.go
new file mode 100644
index 0000000..1a7b3f2
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/config/buildtags/constrainedfoo.go
@@ -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")
+}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/config/buildtags/foo.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/config/buildtags/foo.go
new file mode 100644
index 0000000..69266c7
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/config/buildtags/foo.go
@@ -0,0 +1,7 @@
+// +build !constrained
+
+package config
+
+func foo() {
+ println("foo")
+}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/config/buildtags/go.mod b/pack/acp/start/vim-go/autoload/go/test-fixtures/config/buildtags/go.mod
new file mode 100644
index 0000000..d930dc7
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/config/buildtags/go.mod
@@ -0,0 +1,3 @@
+module config
+
+go 1.13
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/fmt/imports/goimports.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/fmt/imports/goimports.go
deleted file mode 100644
index eec47f8..0000000
--- a/pack/acp/start/vim-go/autoload/go/test-fixtures/fmt/imports/goimports.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package main
-
-import (
- "fmt"
-)
-
-func Foo(log *logging.TestLogger) {
-log.Debug("vim-go")
-}
-
-func main() {
- fmt.Println("vim-go")
-}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/fmt/imports/goimports_golden.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/fmt/imports/goimports_golden.go
deleted file mode 100644
index 3719f6b..0000000
--- a/pack/acp/start/vim-go/autoload/go/test-fixtures/fmt/imports/goimports_golden.go
+++ /dev/null
@@ -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")
-}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/fmt/imports/vendor/gh.com/gi/foo-logging/logger.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/fmt/imports/vendor/gh.com/gi/foo-logging/logger.go
deleted file mode 100644
index 1396018..0000000
--- a/pack/acp/start/vim-go/autoload/go/test-fixtures/fmt/imports/vendor/gh.com/gi/foo-logging/logger.go
+++ /dev/null
@@ -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)
-}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/fmt/src/imports b/pack/acp/start/vim-go/autoload/go/test-fixtures/fmt/src/imports
deleted file mode 100644
index 60ee253..0000000
--- a/pack/acp/start/vim-go/autoload/go/test-fixtures/fmt/src/imports
+++ /dev/null
@@ -1 +0,0 @@
-../imports/
\ No newline at end of file
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/job/dir has spaces/main.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/job/dir has spaces/main.go
new file mode 100644
index 0000000..5d31fef
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/job/dir has spaces/main.go
@@ -0,0 +1,6 @@
+package main
+
+func main() {
+ notafunc()
+ println("vim-go")
+}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/errcheck/compilererror/compilererror.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/errcheck/compilererror/compilererror.go
new file mode 100644
index 0000000..3f3dc7c
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/errcheck/compilererror/compilererror.go
@@ -0,0 +1,7 @@
+package main
+
+import "fmt"
+
+func main() {
+ fmt.Println("vim-go"
+}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/errcheck/errcheck.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/errcheck/errcheck.go
new file mode 100644
index 0000000..72d7fc3
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/errcheck/errcheck.go
@@ -0,0 +1,10 @@
+package errcheck
+
+import (
+ "io"
+ "os"
+)
+
+func foo() {
+ io.Copy(os.Stdout, os.Stdin)
+}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/errcheck/errcheck_test.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/errcheck/errcheck_test.go
new file mode 100644
index 0000000..445d9e5
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/errcheck/errcheck_test.go
@@ -0,0 +1,11 @@
+package errcheck
+
+import (
+ "io"
+ "os"
+ "testing"
+)
+
+func TestFoo(t *testing.T) {
+ io.Copy(os.Stdout, os.Stdin)
+}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/lint/baz.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/lint/baz.go
new file mode 100644
index 0000000..ffff9fe
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/lint/baz.go
@@ -0,0 +1,3 @@
+package lint
+
+func baz() {}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/lint/golangci-lint/problems/importabs/ok.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/lint/golangci-lint/problems/importabs/ok.go
new file mode 100644
index 0000000..8e4d5c6
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/lint/golangci-lint/problems/importabs/ok.go
@@ -0,0 +1,3 @@
+package problems
+
+func bar() {}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/lint/golangci-lint/problems/importabs/problems.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/lint/golangci-lint/problems/importabs/problems.go
new file mode 100644
index 0000000..995f3ee
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/lint/golangci-lint/problems/importabs/problems.go
@@ -0,0 +1,5 @@
+package problems
+
+import "/quux"
+
+func baz() {}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/lint/golangci-lint/problems/multiple/problems.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/lint/golangci-lint/problems/multiple/problems.go
new file mode 100644
index 0000000..4ec38e7
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/lint/golangci-lint/problems/multiple/problems.go
@@ -0,0 +1,9 @@
+package problems
+
+import (
+ "time"
+)
+
+func mySleep(time int) {
+ time.Sleep(500 * time.Millisecond)
+}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/lint/golangci-lint/problems/shadow/problems.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/lint/golangci-lint/problems/shadow/problems.go
new file mode 100644
index 0000000..bdd138a
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/lint/golangci-lint/problems/shadow/problems.go
@@ -0,0 +1,5 @@
+package problems
+
+func mySleep(time int) {
+ time.Sleep(500 * time.Millisecond)
+}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/vet/compilererror/compilererror.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/vet/compilererror/compilererror.go
new file mode 100644
index 0000000..3f3dc7c
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/lint/src/vet/compilererror/compilererror.go
@@ -0,0 +1,7 @@
+package main
+
+import "fmt"
+
+func main() {
+ fmt.Println("vim-go"
+}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/lsp/fmt/format.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/lsp/fmt/format.go
new file mode 100644
index 0000000..3be42f6
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/lsp/fmt/format.go
@@ -0,0 +1,7 @@
+package main
+
+import "fmt"
+
+ func main() {
+fmt.Println("vim-go")
+}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/lsp/fmt/format_golden.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/lsp/fmt/format_golden.go
new file mode 100644
index 0000000..50e8d8d
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/lsp/fmt/format_golden.go
@@ -0,0 +1,7 @@
+package main
+
+import "fmt"
+
+func main() {
+ fmt.Println("vim-go")
+}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/lsp/fmt/newline.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/lsp/fmt/newline.go
new file mode 100644
index 0000000..21948d6
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/lsp/fmt/newline.go
@@ -0,0 +1,6 @@
+package main
+
+import "fmt"
+func main() {
+ fmt.Println("vim-go")
+}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/lsp/imports/imports.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/lsp/imports/imports.go
new file mode 100644
index 0000000..f068a3e
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/lsp/imports/imports.go
@@ -0,0 +1,10 @@
+package main
+
+import (
+ "fmt"
+)
+
+func main() {
+ io.Copy(ioutil.Discard, os.Stdin)
+ fmt.Println("vim-go")
+}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/lsp/imports/imports_golden.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/lsp/imports/imports_golden.go
new file mode 100644
index 0000000..5175514
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/lsp/imports/imports_golden.go
@@ -0,0 +1,13 @@
+package main
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+)
+
+func main() {
+ io.Copy(ioutil.Discard, os.Stdin)
+ fmt.Println("vim-go")
+}
diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/test/src/example/example_test.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/test/src/example/example_test.go
new file mode 100644
index 0000000..bdcc2e1
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/test/src/example/example_test.go
@@ -0,0 +1,10 @@
+package main
+
+import (
+ "fmt"
+)
+
+func ExampleHelloWorld() {
+ fmt.Println("Hello, World")
+ // Output: What's shakin
+}
diff --git a/pack/acp/start/vim-go/autoload/go/test.vim b/pack/acp/start/vim-go/autoload/go/test.vim
index f33d7d9..89dcda6 100644
--- a/pack/acp/start/vim-go/autoload/go/test.vim
+++ b/pack/acp/start/vim-go/autoload/go/test.vim
@@ -6,7 +6,10 @@ set cpo&vim
" compile the tests instead of running them (useful to catch errors in the
" test files). Any other argument is appended to the final `go test` command.
function! go#test#Test(bang, compile, ...) abort
- let args = ["test", '-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.
if a:compile
@@ -32,6 +35,7 @@ function! go#test#Test(bang, compile, ...) abort
if go#config#TermEnabled()
call go#term#new(a:bang, ["go"] + args, s:errorformat())
+ return
endif
if go#util#has_job()
@@ -76,7 +80,7 @@ function! go#test#Test(bang, compile, ...) abort
if l:err != 0
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)
call go#list#Window(l:listtype, len(errors))
if empty(errors)
@@ -166,9 +170,17 @@ function! s:errorformat() abort
let format .= ",%-G" . indent . "%#--- PASS: %.%#"
" 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
- " by a space the duration of the test in parentheses
+ " by a space, followed by the duration of the test in parentheses.
"
" e.g.:
" '--- FAIL: TestSomething (0.00s)'
@@ -207,6 +219,14 @@ function! s:errorformat() abort
let format .= ",%G" . indent . "%#%\\t%\\{2}%m"
" }}}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
" 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
diff --git a/pack/acp/start/vim-go/autoload/go/test_test.vim b/pack/acp/start/vim-go/autoload/go/test_test.vim
index 28d7993..322abed 100644
--- a/pack/acp/start/vim-go/autoload/go/test_test.vim
+++ b/pack/acp/start/vim-go/autoload/go/test_test.vim
@@ -66,9 +66,9 @@ endfunc
func! Test_GoTestShowName() abort
let expected = [
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'TestHelloWorld'},
- \ {'lnum': 6, 'bufnr': 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': 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
@@ -78,20 +78,27 @@ endfunc
func! Test_GoTestVet() abort
let expected = [
- \ {'lnum': 6, 'bufnr': 10, 'col': 2, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'Errorf format %v reads arg #1, but call has 0 args'},
+ \ {'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)
endfunc
func! Test_GoTestTestCompilerError() abort
let expected = [
- \ {'lnum': 10, 'bufnr': 8, 'col': 16, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'cannot use r (type struct {}) as type io.Reader in argument to ioutil.ReadAll:'},
+ \ {'lnum': 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)'}
\ ]
call s:test('testcompilerror/testcompilerror_test.go', expected)
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
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/test'
silent exe 'e ' . $GOPATH . '/src/' . a:file
diff --git a/pack/acp/start/vim-go/autoload/go/tool.vim b/pack/acp/start/vim-go/autoload/go/tool.vim
index fdeaaf1..fccf433 100644
--- a/pack/acp/start/vim-go/autoload/go/tool.vim
+++ b/pack/acp/start/vim-go/autoload/go/tool.vim
@@ -82,14 +82,16 @@ endfunction
function! go#tool#Info(showstatus) abort
let l:mode = go#config#InfoMode()
- if l:mode == 'gocode'
- call go#complete#Info(a:showstatus)
- elseif l:mode == 'guru'
+ if l:mode == 'guru'
call go#guru#DescribeInfo(a:showstatus)
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)
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
endfunction
@@ -115,7 +117,15 @@ endfunction
function! go#tool#DescribeBalloon()
let l:fname = fnamemodify(bufname(v:beval_bufnr), ':p')
- call go#lsp#Hover(l:fname, v:beval_lnum, v:beval_col, funcref('s:balloon', []))
+
+ 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 ''
endfunction
diff --git a/pack/acp/start/vim-go/autoload/go/uri.vim b/pack/acp/start/vim-go/autoload/go/uri.vim
index e8f38ab..74d72fb 100644
--- a/pack/acp/start/vim-go/autoload/go/uri.vim
+++ b/pack/acp/start/vim-go/autoload/go/uri.vim
@@ -18,7 +18,7 @@ function! s:encode(value, unreserved)
return substitute(
\ a:value,
\ a:unreserved,
- \ '\="%".printf(''%02X'', char2nr(submatch(0)))',
+ \ '\=s:encodechar(submatch(0))',
\ 'g'
\)
endfunction
@@ -27,10 +27,27 @@ function! go#uri#Decode(value) abort
return substitute(
\ a:value,
\ '%\(\x\x\)',
- \ '\=nr2char(''0X'' . submatch(1))',
+ \ '\=s:decodehex(submatch(1))',
\ 'g'
\)
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
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/pack/acp/start/vim-go/autoload/go/uri_test.vim b/pack/acp/start/vim-go/autoload/go/uri_test.vim
new file mode 100644
index 0000000..279f045
--- /dev/null
+++ b/pack/acp/start/vim-go/autoload/go/uri_test.vim
@@ -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
diff --git a/pack/acp/start/vim-go/autoload/go/util.vim b/pack/acp/start/vim-go/autoload/go/util.vim
index bccb934..d1cc48d 100644
--- a/pack/acp/start/vim-go/autoload/go/util.vim
+++ b/pack/acp/start/vim-go/autoload/go/util.vim
@@ -36,15 +36,9 @@ function! go#util#Join(...) abort
endfunction
" 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
- let win = ['win16', 'win32', 'win64', 'win95']
- for w in win
- if (has(w))
- return 1
- endif
- endfor
-
- return 0
+ return has('win32')
endfunction
" 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'
" parameters will be used, which wasn't added until a later version.
function! go#util#has_job(...) abort
- if has('nvim')
- return 1
- endif
-
- " cwd and env parameters to job_start was added in this version.
- if a:0 > 0 && a:1 is 1
- return has('job') && has("patch-8.0.0902")
- endif
-
- " job was introduced in 7.4.xxx however there are multiple bug fixes and one
- " of the latest is 8.0.0087 which is required for a stable async API.
- return has('job') && has("patch-8.0.0087")
+ return has('job') || has('nvim')
endfunction
let s:env_cache = {}
@@ -137,9 +120,39 @@ function! go#util#gomod() abort
return substitute(s:exec(['go', 'env', 'GOMOD'])[0], '\n', '', 'g')
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
- return go#util#env("goos") . '_' . go#util#env("goarch")
+" hostosarch returns the OS and ARCH values that the go binary is intended for.
+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
" Run a shell command.
@@ -157,6 +170,13 @@ function! s:system(cmd, ...) abort
set shell=/bin/sh shellredir=>%s\ 2>&1 shellcmdflag=-c
endif
+ if go#util#IsWin()
+ if executable($COMSPEC)
+ let &shell = $COMSPEC
+ set shellcmdflag=/C
+ endif
+ endif
+
try
return call('system', [a:cmd] + a:000)
finally
@@ -196,18 +216,25 @@ function! go#util#Exec(cmd, ...) abort
return call('s:exec', [[l:bin] + a:cmd[1:]] + a:000)
endfunction
+" ExecInDir will execute cmd with the working directory set to the current
+" buffer's directory.
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]
endif
- let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
- let dir = getcwd()
+ let l:dir = go#util#Chdir(a:wd)
try
- execute cd . fnameescape(expand("%:p:h"))
let [l:out, l:err] = call('go#util#Exec', [a:cmd] + a:000)
finally
- execute cd . fnameescape(l:dir)
+ call go#util#Chdir(l:dir)
endtry
return [l:out, l:err]
endfunction
@@ -446,7 +473,7 @@ function! go#util#tempdir(prefix) abort
endif
" 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)
call mkdir(l:tmp, 'p', 0700)
return l:tmp
@@ -519,6 +546,170 @@ function! go#util#ShowInfo(info)
echo "vim-go: " | echohl Function | echon a:info | echohl None
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
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/pack/acp/start/vim-go/autoload/gotest.vim b/pack/acp/start/vim-go/autoload/gotest.vim
index e010d52..4ee727a 100644
--- a/pack/acp/start/vim-go/autoload/gotest.vim
+++ b/pack/acp/start/vim-go/autoload/gotest.vim
@@ -18,16 +18,23 @@ fun! gotest#write_file(path, contents) abort
call mkdir(fnamemodify(l:full_path, ':h'), 'p')
call writefile(a:contents, l:full_path)
- exe 'cd ' . l:dir . '/src'
+ call go#util#Chdir(l:dir . '/src')
+
silent exe 'e! ' . a:path
" Set cursor.
let l:lnum = 1
for l:line in a:contents
- let l:m = match(l:line, "\x1f")
+ let l:m = stridx(l:line, "\x1f")
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", '', ''))
+ silent noautocmd w!
+
+ call go#lsp#DidClose(expand('%:p'))
+ call go#lsp#DidOpen(expand('%:p'))
+
break
endif
@@ -42,15 +49,21 @@ endfun
" The file will be copied to a new GOPATH-compliant temporary directory and
" loaded as the current buffer.
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 $GOPATH .= ':' . l:dir
let l:full_path = l:dir . '/src/' . a:path
call mkdir(fnamemodify(l:full_path, ':h'), 'p')
- exe 'cd ' . l:dir . '/src'
- silent exe 'noautocmd e ' . a:path
+ call go#util#Chdir(l:dir . '/src')
+ silent exe 'noautocmd e! ' . a:path
silent exe printf('read %s/test-fixtures/%s', g:vim_go_root, a:path)
silent noautocmd w!
+ if go#util#has_job()
+ call go#lsp#AddWorkspaceDirectory(fnamemodify(l:full_path, ':p:h'))
+ endif
return l:dir
endfun
@@ -109,26 +122,29 @@ endfun
func! gotest#assert_quickfix(got, want) abort
call assert_equal(len(a:want), len(a:got), "number of errors")
if len(a:want) != len(a:got)
- call assert_equal(a:want, a:got)
- return
+ return assert_equal(a:want, a:got)
endif
+ let l:retval = 0
let i = 0
+
while i < len(a:want)
let want_item = a:want[i]
let got_item = a:got[i]
let i += 1
- call assert_equal(want_item.bufnr, got_item.bufnr, "bufnr")
- call assert_equal(want_item.lnum, got_item.lnum, "lnum")
- call assert_equal(want_item.col, got_item.col, "col")
- call assert_equal(want_item.vcol, got_item.vcol, "vcol")
- call assert_equal(want_item.nr, got_item.nr, "nr")
- call assert_equal(want_item.pattern, got_item.pattern, "pattern")
- call assert_equal(want_item.text, got_item.text, "text")
- call assert_equal(want_item.type, got_item.type, "type")
- call assert_equal(want_item.valid, got_item.valid, "valid")
+ let l:retval = assert_equal(want_item.bufnr, got_item.bufnr, "bufnr") || l:retval
+ let l:retval = assert_equal(want_item.lnum, got_item.lnum, "lnum") || l:retval
+ let l:retval = assert_equal(want_item.col, got_item.col, "col") || l:retval
+ let l:retval = assert_equal(want_item.vcol, got_item.vcol, "vcol") || l:retval
+ let l:retval = assert_equal(want_item.nr, got_item.nr, "nr") || l:retval
+ let l:retval = assert_equal(want_item.pattern, got_item.pattern, "pattern") || l:retval
+ let l:retval = assert_equal(want_item.text, got_item.text, "text") || l:retval
+ let l:retval = assert_equal(want_item.type, got_item.type, "type") || l:retval
+ let l:retval = assert_equal(want_item.valid, got_item.valid, "valid") || l:retval
endwhile
+
+ return l:retval
endfunc
" restore Vi compatibility settings
diff --git a/pack/acp/start/vim-go/compiler/go.vim b/pack/acp/start/vim-go/compiler/go.vim
index 5f978bb..0bd6a15 100644
--- a/pack/acp/start/vim-go/compiler/go.vim
+++ b/pack/acp/start/vim-go/compiler/go.vim
@@ -31,14 +31,13 @@ endif
" use a different output, for those we define them directly and modify the
" errorformat ourselves. More information at:
" http://vimdoc.sourceforge.net/htmldoc/quickfix.html#errorformat
-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+=%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:\ %m " Start of multiline unspecified string is 'filename:linenumber:'
-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#\ %.%# " Ignore lines beginning with '#' ('# command-line-arguments' line sometimes appears?)
+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+=%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+=%C%*\\s%m " Continuation of multiline error message is indented
+CompilerSet errorformat+=%-G%.%# " All lines not matching any of the above patterns are ignored
let &cpo = s:save_cpo
unlet s:save_cpo
diff --git a/pack/acp/start/vim-go/doc/vim-go.txt b/pack/acp/start/vim-go/doc/vim-go.txt
index 3dd3798..14e8062 100644
--- a/pack/acp/start/vim-go/doc/vim-go.txt
+++ b/pack/acp/start/vim-go/doc/vim-go.txt
@@ -32,17 +32,18 @@ CONTENTS *go-contents*
INTRO *go-intro*
Go (golang) support for Vim. vim-go comes with sensible predefined settings
-(e.g. automatic `gofmt` on save), has autocomplete, snippet support, improved
-syntax highlighting, go toolchain commands, etc. It is highly customizable,
-and individual features can be toggled easily. vim-go leverages a number of
-tools developed by the Go community to provide a seamless Vim experience.
+(e.g. automatic `gofmt` on save), has code completion, snippet support,
+improved syntax highlighting, go toolchain commands, etc. It is highly
+customizable, and individual features can be toggled easily. vim-go leverages
+a number of tools developed by the Go community to provide a seamless Vim
+experience.
* Compile your package with |:GoBuild|, install it with |:GoInstall| or
test it with |:GoTest|. Run a single test with |:GoTestFunc|).
* Quickly execute your current file(s) with |:GoRun|.
* Improved syntax highlighting and folding.
* Debug programs with integrated `delve` support with |:GoDebugStart|.
- * Completion support via `gocode` and `gopls`.
+ * Code completion support via `gocode` and `gopls`.
* `gofmt` or `goimports` on save keeps the cursor position and undo history.
* Go to symbol/declaration with |:GoDef|.
* Look up documentation with |:GoDoc| or |:GoDocBrowser|.
@@ -50,7 +51,7 @@ tools developed by the Go community to provide a seamless Vim experience.
* Precise type-safe renaming of identifiers with |:GoRename|.
* See which code is covered by tests with |:GoCoverage|.
* 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
quickfix or location list.
* Lint your code with |:GoLint|, run your code through |:GoVet| to catch
@@ -76,7 +77,7 @@ tools developed by the Go community to provide a seamless Vim experience.
==============================================================================
INSTALL *go-install*
-vim-go requires at least Vim 7.4.2009 or Neovim 0.3.1. On macOS, if you are
+vim-go requires at least Vim 8.0.1453 or Neovim 0.4.0. On macOS, if you are
still using your system version of vim, you can use homebrew to keep your
version of Vim up-to-date with the following terminal command:
>
@@ -93,26 +94,26 @@ For Pathogen or Vim |packages|, just clone the repo. For other plugin managers
you may also need to add the lines to your vimrc to execute the plugin
manager's install command.
-* Vim 8 |packages|
->
+* Vim 8 |packages| >
+
git clone https://github.com/fatih/vim-go.git \
~/.vim/pack/plugins/start/vim-go
-
+<
* https://github.com/tpope/vim-pathogen >
git clone https://github.com/fatih/vim-go.git ~/.vim/bundle/vim-go
<
* https://github.com/junegunn/vim-plug >
- Plug 'fatih/vim-go'
-
+ Plug 'fatih/vim-go', { 'do': ':GoUpdateBinaries' }
+<
* https://github.com/Shougo/neobundle.vim >
NeoBundle 'fatih/vim-go'
<
* https://github.com/gmarik/vundle >
- Plugin 'fatih/vim-go', { 'do': ':GoUpdateBinaries' }
+ Plugin 'fatih/vim-go'
<
* Manual (not recommended) >
@@ -127,7 +128,7 @@ or $GOPATH/bin (default: $HOME/go/bin). It requires `git`.
Depending on your installation method, you may have to generate the plugin's
|:helptags| manually (e.g. `:helptags ALL`).
-Autocompletion is enabled by default via 'omnifunc', which you can trigger
+Code completion is enabled by default via 'omnifunc', which you can trigger
with |i_CTRL-X_CTRL-O| (``).
Supported Go plugins~ *vim-go-plugins*
@@ -215,7 +216,7 @@ COMMANDS *go-commands*
Open the relevant GoDoc in browser for either the word[s] passed to the
command or by default, the word under the cursor. By default it opens the
- documentation in 'https://godoc.org'. To change it see |'g:go_doc_url'|.
+ documentation in 'https://pkg.go.dev'. To change it see |'g:go_doc_url'|.
*:GoFmt*
:GoFmt
@@ -248,9 +249,7 @@ COMMANDS *go-commands*
not guarantee all reports are genuine problems, but it can find errors not
caught by the compilers.
- You may optionally pass any valid go tool vet flags/options. In this case,
- `go tool vet` is run in place of `go vet`. For a full list please see
- `go tool vet -h`.
+ You may optionally pass any valid go vet flags/options.
If [!] is not given the first error is jumped to.
@@ -344,6 +343,9 @@ CTRL-t
If using neovim then `:GoRun` will run in a new terminal according to
|'g:go_term_mode'|.
+ The working directory will be the directory containing the current buffer.
+
+
*:GoBuild*
:GoBuild[!] [expand]
@@ -375,8 +377,8 @@ CTRL-t
:GoInfo
Show type information about the identifier under the cursor. For example
putting it above a function call is going to show the full function
- signature. By default it uses `gocode` to get the type informations. To
- change the underlying tool from `gocode` to another tool, see
+ signature. By default it uses `gopls` to get the type informations. To
+ change the underlying tool from `gopls` to another tool, see
|'g:go_info_mode'|.
@@ -400,7 +402,7 @@ CTRL-t
You may optionally pass any valid go test flags/options. For a full list
please see `go help test`.
- GoTest timesout automatically after 10 seconds. To customize the timeout
+ GoTest times out automatically after 10 seconds. To customize the timeout
use |'g:go_test_timeout'|. This feature is disabled if any arguments are
passed to the `:GoTest` command.
@@ -503,7 +505,7 @@ CTRL-t
:GoInstallBinaries [binaries]
Download and install all necessary Go tool binaries such as `godef`,
- `goimports`, `gocode`, etc. under |'g:go_bin_path'|. If [binaries] is
+ `goimports`, `gopls`, etc. under |'g:go_bin_path'|. If [binaries] is
supplied, then only the specified binaries will be installed. The default
is to install everything.
@@ -513,7 +515,7 @@ CTRL-t
:GoUpdateBinaries [binaries]
Download and update previously installed Go tool binaries such as `godef`,
- `goimports`, `gocode`, etc. under |'g:go_bin_path'|. If [binaries] is
+ `goimports`, `gopls`, etc. under |'g:go_bin_path'|. If [binaries] is
supplied, then only the specified binaries will be updated. The default is
to update everything.
@@ -535,7 +537,7 @@ CTRL-t
*:GoGuruScope*
-:GoGuruScope [pattern] [pattern2] ... [patternN]
+:GoGuruScope [pattern] ...
Changes the custom |'g:go_guru_scope'| setting and overrides it with the
given package patterns. The custom scope is cleared (unset) if `""` is
@@ -621,8 +623,7 @@ CTRL-t
:GoReferrers
The referrers query shows the set of identifiers that refer to the same
- object as does the selected identifier, within any package in the analysis
- scope.
+ object as does the selected identifier.
*:GoSameIds*
:GoSameIds
@@ -647,19 +648,31 @@ CTRL-t
the cursor. This basically toggles the option |'g:go_auto_sameids'|
on/off.
If enabled it starts highlighting whenever your cursor is staying at the
- same position for a configurable period of time (see 'updatetime'). If
- disabled it clears and stops automatic highlighting.
+ same position for a configurable period of time (see |'g:go_updatetime'|).
+ If disabled it clears and stops automatic highlighting.
*:GoMetaLinter*
:GoMetaLinter! [path]
- Calls the underlying `gometalinter` tool and displays all warnings and
+ Calls the underlying `golangci-lint` tool and displays all warnings and
errors in the |quickfix| window. By default the following linters are
enabled: `vet`, `golint`, and `errcheck`. This can be changed with the
|'g:go_metalinter_enabled'| variable. To override the command completely
use the variable |'g:go_metalinter_command'|. To override the maximum
linters execution time use |'g:go_metalinter_deadline'| variable.
+ If [!] is not given the first error is jumped to.
+
+ *:GoDiagnostics*
+:GoDiagnostics! [packages]
+
+ Displays the diagnostics from `gopls` for the given packages in a
+ |quickfix| window. The diagnostics for the current package are displayed
+ when no package is given. The diagnostics for all packages will be
+ displayed when `all` is as an argument.
+
+ Disabled when |'g:go_diagnostics_enabled'| is not set.
+
If [!] is not given the first error is jumped to.
*:GoBuildTags*
@@ -667,8 +680,8 @@ CTRL-t
Changes the build tags for various commands. If you have any file that
uses a custom build tag, such as `// +build integration` , this command
- can be used to pass it to all tools that accepts tags, such as guru,
- gorename, etc.
+ can be used to pass it to all tools that accepts tags, such as gopls,
+ guru, gorename, etc.
The build tags is cleared (unset) if `""` is given. If no arguments are
given it prints the current build tags.
@@ -787,7 +800,7 @@ CTRL-t
You can define a constant value instead of the default field based value.
For example the following command will add ``valid:"1"`` to all fields.
>
- :GoAddTags valid=1
+ :GoAddTags valid:1
<
*:GoRemoveTags*
:[range]GoRemoveTags [key],[option] [key1],[option1] ...
@@ -834,7 +847,7 @@ CTRL-t
Toggles |'g:go_metalinter_autosave'|.
- By default, `gometalinter` messages will be shown in the |location-list|
+ By default, `golangci-lint` messages will be shown in the |location-list|
window. The list to use can be set using |'g:go_list_type_commands'|.
*:GoTemplateAutoCreateToggle*
@@ -905,6 +918,16 @@ CTRL-t
tries to preserve cursor position and avoids replacing the buffer with
stderr output.
+ *:GoAddWorkspace*
+:GoAddWorkspace [dir] ...
+
+ Add directories to the `gopls` workspace.
+
+ *:GoLSPDebugBrowser*
+:GoLSPDebugBrowser
+
+ Open a browser to see gopls debugging information.
+
==============================================================================
MAPPINGS *go-mappings*
@@ -1028,7 +1051,7 @@ Show the relevant GoDoc for the word under in browser
*(go-def)*
-Goto declaration/definition. Results are shown in the current buffer.
+Goto declaration/definition. Results are shown in the current window.
*(go-def-split)*
@@ -1043,6 +1066,24 @@ Jumps to an existing buffer if |'g:go_def_reuse_buffer'| is enabled.
*(go-def-tab)*
Goto declaration/definition. Results are shown in a tab window.
+Jumps to an existing buffer if |'g:go_def_reuse_buffer'| is enabled.
+
+ *(go-def-type)*
+
+Goto type declaration/definition. Results are shown in the current window.
+Jumps to an existing buffer if |'g:go_def_reuse_buffer'| is enabled.
+
+ *(go-def-type-vertical)*
+Goto type declaration/definition. Results are shown in a vertical split
+window.
+Jumps to an existing buffer if |'g:go_def_reuse_buffer'| is enabled.
+
+ *(go-def-type-split)*
+Goto type declaration/definition. Results are shown in a split window.
+Jumps to an existing buffer if |'g:go_def_reuse_buffer'| is enabled.
+
+ *(go-def-type-tab)*
+Goto type declaration/definition. Results are shown in a tab window.
Jumps to an existing buffer if |'g:go_def_reuse_buffer'| is enabled.
*(go-def-stack)*
@@ -1126,6 +1167,9 @@ return values and the numbers.
Calls |:GoModFmt| for the current buffer
+ *(go-diagnostics)*
+Calls `:GoDiagnostics`
+
==============================================================================
TEXT OBJECTS *go-text-objects*
@@ -1187,13 +1231,7 @@ into the statusline.
*go#complete#Complete()*
-Uses `gopls` for autocompletion. By default, it is hooked up to |'omnifunc'|
-for Vim8 and Neovim.
-
- *go#complete#GocodeComplete()*
-
-Uses `gocode` for autocompletion. By default, it is hooked up to |'omnifunc'|
-for Vim 7.4.
+Uses `gopls` for autocompletion. By default, it is hooked up to 'omnifunc'.
*go#tool#DescribeBalloon()*
@@ -1203,6 +1241,29 @@ balloonexpr`.
==============================================================================
SETTINGS *go-settings*
+ *'g:go_version_warning'*
+
+Enable warning when using an unsupported version of Vim. By default it is
+enabled.
+>
+ let g:go_version_warning = 1
+<
+
+ *'g:go_code_completion_enabled'*
+
+Enable code completion with 'omnifunc'. By default it is enabled.
+>
+ let g:go_code_completion_enabled = 1
+<
+
+ *'g:go_code_completion_icase'*
+
+Override the icase field in 'omnifunc' results. By default it is set to 0.
+See 'complete-items' for details.
+>
+ let g:go_code_completion_icase = 0
+<
+
*'g:go_test_show_name'*
Show the name of each failed test before the errors and logs output by the
@@ -1220,9 +1281,9 @@ set to 10 seconds . >
<
*'g:go_play_browser_command'*
-Browser to use for |:GoPlay| or |:GoDocBrowser|. The url must be added with
-`%URL%`, and it's advisable to include `&` to make sure the shell returns. For
-example:
+Browser to use for |:GoPlay|, |:GoDocBrowser|, and |:GoLSPDebugBrowser|. The
+url must be added with `%URL%`, and it's advisable to include `&` to make sure
+the shell returns. For example:
>
let g:go_play_browser_command = 'firefox-developer %URL% &'
<
@@ -1243,7 +1304,7 @@ with |:GoPlay|. By default it's enabled. >
Use this option to show the type info (|:GoInfo|) for the word under the
cursor automatically. Whenever the cursor changes the type info will be
updated. By default it's disabled. The delay can be configured with the
-'g:go_updatetime' setting.
+|'g:go_updatetime'| setting.
>
let g:go_auto_type_info = 0
<
@@ -1251,48 +1312,54 @@ updated. By default it's disabled. The delay can be configured with the
*'g:go_info_mode'*
Use this option to define the command to be used for |:GoInfo|. By default
-`gocode` is being used as it's the fastest option. But one might also use
-`gopls` or `guru` as they cover more cases and are more accurate. Current
-valid options are: `[gocode, guru, gopls]` >
-
- let g:go_info_mode = 'gocode'
+`gopls` is used, because it is the fastest and is known to be highly accurate.
+One might also use `guru` for its accuracy.
+Valid options are `gopls` and `guru`.
+>
+ let g:go_info_mode = 'gopls'
<
*'g:go_auto_sameids'*
Use this option to highlight all uses of the identifier under the cursor
-(:GoSameIds) automatically. By default it's disabled. The delay can be
-configured with the 'g:go_updatetime' setting.
+(|:GoSameIds|) automatically. By default it's disabled. The delay can be
+configured with the |'g:go_updatetime'| setting.
>
let g:go_auto_sameids = 0
<
*'g:go_updatetime'*
-Use this option to configure the a custom 'updatetime' for Go source files. If
-set to 0, no custom time will be configured. By default it's set to 800ms.
+Use this option to configure the delay until it starts some jobs (see
+|'g:go_auto_type_info'|, |'g:go_auto_sameids'|). If set to 0, it uses the
+value from 'updatetime'. By default it's set to 800ms.
>
let g:go_updatetime = 800
<
*'g:go_jump_to_error'*
Use this option to enable/disable passing the bang attribute to the mappings
-|(go-build)|, |(go-run)|, etc.. When enabled it will jump to the first error
-automatically (means it will NOT pass the bang attribute to the appropriate
-command, i.e: (go-run) -> :GoRun ). Note, that calling this doesn't have any
-affect on calling the commands manually. This setting is only useful for
-changing the behaviour of our custom static mappings. By default it's enabled.
+(e.g. |(go-build)|, |(go-run)|, etc.) and the metalinter on save. When
+enabled it will jump to the first error automatically (means it will NOT pass
+the bang attribute to the appropriate command, i.e: (go-run) -> :GoRun ).
+Note, that calling this doesn't have any affect on calling the commands
+manually. This setting is only useful for changing the behaviour of our custom
+static mappings. By default it's enabled.
>
let g:go_jump_to_error = 1
<
*'g:go_fmt_autosave'*
-Use this option to auto |:GoFmt| on save. By default it's enabled >
+Use this option to auto |:GoFmt| on save. When both 'g:go_imports_autosave'
+and 'g:go_fmt_autosave' are enabled and both 'g:go_fmt_command' and
+'g:go_imports_mode' are set to `goimports`, `goimports` will be run only once.
+By default it's enabled >
let g:go_fmt_autosave = 1
<
*'g:go_fmt_command'*
-Use this option to define which tool is used to gofmt. By default `gofmt` is
-used >
+Use this option to define which tool is used to format code. Valid options are
+`gofmt`, `goimports`, and `gopls`. By default `gofmt` is used.
+>
let g:go_fmt_command = "gofmt"
<
@@ -1315,6 +1382,19 @@ The dictionary version allows you to define options for multiple binaries:
\ 'gofmt': '-s',
\ 'goimports': '-local mycompany.com',
\ }
+<
+ *'b:go_fmt_options'*
+
+This option is identical to |'g:go_fmt_options'|, but a buffer-level setting.
+If present, it's used instead of the global setting. By default it is not set.
+
+As an example, the following autocmd will configure goimports to put imports
+of packages from the current module in their own group:
+>
+ autocmd FileType go let b:go_fmt_options = {
+ \ 'goimports': '-local ' .
+ \ trim(system('{cd '. shellescape(expand('%:h')) .' && go list -m;}')),
+ \ }
<
*'g:go_fmt_fail_silently'*
@@ -1328,10 +1408,31 @@ fails. By default the location list is shown. >
Use this option to enable fmt's experimental mode. This experimental mode is
superior to the current mode as it fully saves the undo history, so undo/redo
doesn't break. However, it's slow (creates/deletes a file for every save) and
-it's causing problems on some Vim versions. By default it's disabled. >
+it's causing problems on some Vim versions. This has no effect if
+`g:go_fmt_command` is set to `gopls`. By default it's disabled.
+>
let g:go_fmt_experimental = 0
+<
+
+ *'g:go_imports_autosave'*
+
+Use this option to auto |:GoImports| on save. When both
+'g:go_imports_autosave' and 'g:go_fmt_autosave' are enabled and both
+'g:go_fmt_command' and 'g:go_imports_mode' are set to `goimports`, `goimports`
+will be run only once. By default it's disabled.
+>
+ let g:go_imports_autosave = 0
+<
+ *'g:go_imports_mode'*
+
+Use this option to define which tool is used to adjust imports. Valid options
+are `goimports` and `gopls`. The buffer will not be formatted when this is set
+to `gopls`. By default `goimports` is used.
+>
+
+ let g:go_imports_mode = "goimports"
<
*'g:go_mod_fmt_autosave'*
@@ -1344,7 +1445,7 @@ Use this option to auto |:GoModFmt| on save. By default it's enabled >
Use this option to run `godoc` on words under the cursor with |K|; this will
normally run the `man` program, but for Go using `godoc` is more idiomatic. It
-will not override the |'keywordprg'| setting, but will run |:GoDoc|. Default
+will not override the 'keywordprg' setting, but will run |:GoDoc|. Default
is enabled. >
let g:go_doc_keywordprg_enabled = 1
@@ -1359,18 +1460,45 @@ Maximum height for the GoDoc window created with |:GoDoc|. Default is 20. >
*'g:go_doc_url'*
godoc server URL used when |:GoDocBrowser| is used. Change if you want to use
-a private internal service. Default is 'https://godoc.org'.
+a private internal service. Default is 'https://pkg.go.dev'.
>
- let g:go_doc_url = 'https://godoc.org'
+ let g:go_doc_url = 'https://pkg.go.dev'
+<
+
+ *'g:go_doc_popup_window'*
+
+Use this option to use the popup-window for |K| and |:GoDoc|, rather than the
+|preview-window|. Default is disabled.
+>
+ let g:go_doc_popup_window = 0
<
*'g:go_def_mode'*
Use this option to define the command to be used for |:GoDef|. By default
-`guru` is being used as it covers all edge cases. But one might also use
-`godef` as it's faster. Current valid options are: `[guru, godef, gopls]` >
+`gopls` is used, because it is the fastest. One might also use `guru` for its
+accuracy or `godef` for its performance. Valid options are `godef`, `gopls`,
+and `guru`.
+>
+ let g:go_def_mode = 'gopls'
+<
+ *'g:go_referrers_mode'*
- let g:go_def_mode = 'guru'
+Use this option to define the command to be used for |:GoReferrers|. By
+default `gopls` is used, because it is the fastest and works with Go modules.
+One might also use `guru` for its ability to show references from other
+packages. This option will be removed after `gopls` can show references from
+other packages. Valid options are `gopls` and `guru`. By default it's `gopls`.
+>
+ let g:go_referrers_mode = 'gopls'
+<
+ *'g:go_implements_mode'*
+
+Use this option to define the command to be used for |:GoImplements|.
+The Implements feature in gopls is still new and being worked upon.
+Valid options are `gopls` and `guru`. By default it's `guru`.
+>
+ let g:go_implements_mode = 'guru'
<
*'g:go_def_mapping_enabled'*
@@ -1439,7 +1567,7 @@ pattern. An example input might be:
Also see |go-guru-scope|.
-By default it's not set, so the relevant commands defaults are being used.
+By default it's not set, so the relevant commands' defaults are being used.
>
let g:go_guru_scope = []
<
@@ -1487,7 +1615,7 @@ function when using the `af` text object. By default it's enabled. >
Use this option to auto |:GoMetaLinter| on save. Only linter messages for
the active buffer will be shown.
-By default, `gometalinter` messages will be shown in the |location-list|
+By default, `golangci-lint` messages will be shown in the |location-list|
window. The list to use can be set using |'g:go_list_type_commands'|.
By default it's disabled >
@@ -1496,32 +1624,28 @@ window. The list to use can be set using |'g:go_list_type_commands'|.
*'g:go_metalinter_autosave_enabled'*
Specifies the enabled linters for auto |:GoMetaLinter| on save. By
-default it's using `vet` and `golint`.
+default it's using `vet` and `golint`. If any are enabled, `--disable-all`
+will be sent to the metalinter.
>
let g:go_metalinter_autosave_enabled = ['vet', 'golint']
<
*'g:go_metalinter_enabled'*
Specifies the linters to enable for the |:GoMetaLinter| command. By default
-it's using `vet`, `golint` and `errcheck`.
+it's using `vet`, `golint` and `errcheck`. If any are enabled, `--disable-all`
+will be sent to the metalinter.
>
let g:go_metalinter_enabled = ['vet', 'golint', 'errcheck']
-<
- *'g:go_metalinter_disabled'*
-
-Specifies the linters to disable for the |:GoMetaLinter| command. By default
-it's empty
->
- let g:go_metalinter_disabled = []
<
*'g:go_metalinter_command'*
-Overrides the command to be executed when |:GoMetaLinter| is called. By
-default it's `gometalinter`. `golangci-lint` is also supported. It can also be
-used as an advanced setting for users who want to have more control over
-the metalinter.
+Overrides the command to be executed when |:GoMetaLinter| is called. By
+default it's `golangci-lint`. Valid options are `golangci-lint` and `gopls`.
+When the value is `gopls`, users may want to consider setting
+`g:go_gopls_staticcheck`. It can also be used as an advanced setting for
+users who want to have more control over the metalinter.
>
- let g:go_metalinter_command = "gometalinter"
+ let g:go_metalinter_command = "golangci-lint"
<
*'g:go_metalinter_deadline'*
@@ -1586,19 +1710,29 @@ Use this option to auto |:AsmFmt| on save. By default it's disabled. >
<
*'g:go_term_mode'*
-This option is Neovim only. Use it to change the default command used to
-open a new terminal for go commands such as |:GoRun|.
-The default is vsplit.
+The default command used to open a new terminal for go commands such as
+|:GoRun|. The default is `:vsplit`.
+
+Applicable to Neovim and Vim with `terminal` feature only.
>
let g:go_term_mode = "vsplit"
+<
+ *'g:go_term_reuse'*
+
+Reuse the terminal window when |'g:go_term_enabled'| is set. By default it's
+disabled.
+>
+ let g:go_term_reuse = 0
<
*'g:go_term_height'*
*'g:go_term_width'*
-These options are Neovim only. Use them to control the height and width of
-a terminal split. By default these are not set, meaning that the height and
-width are set automatically by Neovim. The height only applies to a
-horizontal split and width only applies to a vertical split.
+Controls the height and width of a terminal split, respectively. By default
+these are not set, meaning that the height and width are set automatically by
+the editor. The height only applies to a horizontal split and width only
+applies to a vertical split.
+
+Applicable to Neovim and Vim with `terminal` feature only.
For example here is how to set each to 30.
>
@@ -1607,19 +1741,36 @@ For example here is how to set each to 30.
<
*'g:go_term_enabled'*
-This option is Neovim only. Use it to change the behavior of the test
-commands. If set to 1 it opens the test commands inside a new terminal
-according to |'g:go_term_mode'|, otherwise it will run them in the background
-just like `:GoBuild`. By default it is disabled.
+Causes some types of jobs to run inside a new terminal according to
+|'g:go_term_mode'|. By default it is disabled.
+
+Applicable to Neovim and Vim with `terminal` feature only.
>
let g:go_term_enabled = 0
+<
+ *'g:go_term_close_on_exit'*
+
+Closes the terminal after the command run in it exits when the command fails.
+By default it is enabled.
+
+Applicable to Neovim and Vim with `terminal` feature only.
+
+>
+ let g:go_term_close_on_exit = 1
<
*'g:go_alternate_mode'*
-Specifies the command that |:GoAlternate| uses to open the alternate file.
-By default it is set to edit.
+Specifies the command that |:GoAlternate| uses to open the alternate file. By
+default it is set to edit.
>
let g:go_alternate_mode = "edit"
+<
+ *'g:go_rename_command'*
+
+Use this option to define which tool is used to rename. By default `gopls`
+is used. Valid options are `gorename` and `gopls`.
+>
+ let g:go_rename_command = 'gopls'
<
*'g:go_gorename_prefill'*
@@ -1633,42 +1784,130 @@ same.
\ '? go#util#pascalcase(expand(""))' .
\ ': go#util#camelcase(expand(""))'
<
- *'g:go_gocode_propose_builtins'*
-Specifies whether `gocode` should add built-in types, functions and constants
-to an autocompletion proposals. By default it is enabled.
->
- let g:go_gocode_propose_builtins = 1
-<
- *'g:go_gocode_propose_source'*
+ *'g:go_gopls_enabled'*
-Specifies whether `gocode` should use source files instead of binary packages
-for autocompletion proposals. When disabled, only identifiers from the current
-package and packages that have been installed will proposed.
->
- let g:go_gocode_propose_source = 0
-<
- *'g:go_gocode_unimported_packages'*
+Specifies whether `gopls` can be used by vim-go. By default gopls is enabled.
+When gopls is disabled completion will not work and other configuration
+options may also need to be adjusted.
-Specifies whether `gocode` should include suggestions from unimported
-packages. By default it is disabled.
>
- let g:go_gocode_unimported_packages = 0
+ let g:go_gopls_enabled = 1
<
- *'g:go_gocode_socket_type'*
+ *'g:go_gopls_options'*
-Specifies whether `gocode` should use a different socket type. By default
-`unix` is enabled. Possible values: `unix`, `tcp`
+The commandline arguments to pass to gopls. By default, it's `-remote=auto`.
>
- let g:go_gocode_socket_type = 'unix'
+ let g:go_gopls_options = []
<
+
+ *'g:go_gopls_analyses'*
+
+The analyses settings for `gopls`. By default, it's `v:null`. Valid map values
+are `v:true` and `v:false`.
+>
+ let g:go_gopls_analyses = v:null
+<
+
+ *'g:go_gopls_complete_unimported'*
+
+Specifies whether `gopls` should include suggestions from unimported packages.
+When it is `v:null`, `gopls`' default will be used. By default it is
+`v:null`.
+>
+ let g:go_gopls_complete_unimported = v:null
+<
+
+ *'g:go_gopls_deep_completion'*
+
+Specifies whether `gopls` should use deep completion. When it is `v:null`,
+`gopls`' default will be used. By default it is `v:null`.
+
+>
+ let g:go_gopls_deep_completion = v:null
+<
+
+ *'g:go_gopls_matcher'*
+
+Specifies how `gopls` should match for completions. Valid values are `v:null`,
+`fuzzy`, and `caseSensitive`. When it is `v:null`, `gopls`' default will be
+used. By default it is `v:null`.
+>
+ let g:go_gopls_matcher = v:null
+<
+
+ *'g:go_gopls_staticcheck'*
+
+Specifies whether `gopls` should run staticcheck checks. When it is `v:null`,
+`gopls`' default will be used. By default it is `v:null`.
+>
+ let g:go_gopls_staticcheck = v:null
+<
+
+ *'g:go_gopls_use_placeholders'*
+
+Specifies whether `gopls` can provide placeholders for function parameters and
+struct fields. When set, completion items will be treated as anonymous
+snippets if UltiSnips is installed and configured to be used as
+|'g:go_snippet_engine'|. When it is `v:null`, `gopls`' default will be used.
+By default it is `v:null`.
+>
+ let g:go_gopls_use_placeholders = v:null
+<
+
+ *'g:go_gopls_temp_modfile'*
+
+Specifies whether `gopls` should use a temp modfile and suggest edits rather
+than modifying the ambient go.mod file. When it is `v:null`, `gopls`' default
+will be used. By default it is `v:null`.
+>
+ let g:go_gopls_temp_modfile = v:null
+<
+
+ *'g:go_gopls_local'*
+
+Specifies the prefix for imports that `gopls` should consider group
+separately. When it is `v:null`, `gopls`' default will be used. By default it
+is `v:null`.
+>
+ let g:go_gopls_local = v:null
+<
+
+ *'g:go_gopls_gofumpt'*
+
+Specifies whether `gopls` should use `gofumpt` for formatting. When it is
+`v:null`, `gopls`' default will be used. By default it is `v:null`.
+>
+ let g:go_gopls_gofumpt = v:null
+<
+
+ *'g:go_gopls_settings'*
+
+Specifies `gopls` workspace settings for `gopls` that are not yet officially
+supported by vim-go. Any value in the dictionary will be overridden by values
+provided in the specific options supported by vim-go (e.g.
+g:go_gopls_staticcheck) or settings statically configured by vim-go to ensure
+expected behavior. By default it is `v:null`.
+>
+ let g:go_gopls_settings = v:null
+<
+ *'g:go_diagnostics_enabled'*
+
+Specifies whether `gopls` diagnostics are enabled. Only the diagnostics for
+the current buffer will be processed when it is not set; all others will be
+ignored. By default it is disabled.
+>
+ let g:go_diagnostics_enabled = 0
+<
+
*'g:go_template_autocreate'*
When a new Go file is created, vim-go automatically fills the buffer content
with a Go code template. By default, the templates under the `templates`
folder are used. This can be changed with the |'g:go_template_file'| and
-|'g:go_template_test_file'| settings.
+|'g:go_template_test_file'| settings to either use a different file in the
+same `templates` folder, or to use a file stored elsewhere.
If the new file is created in an already prepopulated package (with other Go
files), in this case a Go code template with only the Go package declaration
@@ -1683,17 +1922,23 @@ By default it is enabled.
<
*'g:go_template_file'*
-Specifies the file under the `templates` folder that is used if a new Go file
-is created. Checkout |'g:go_template_autocreate'| for more info. By default
-the `hello_world.go` file is used.
+Specifies either the file under the `templates` folder that is used if a new
+Go file is created. Checkout |'g:go_template_autocreate'| for more info. By
+default the `hello_world.go` file is used.
+
+This variable can be set to an absolute path, so the template files don't have
+to be stored inside the vim-go directory structure. Useful when you want to
+use different templates for different projects.
>
let g:go_template_file = "hello_world.go"
<
*'g:go_template_test_file'*
-Specifies the file under the `templates` folder that is used if a new Go test
-file is created. Checkout |'g:go_template_autocreate'| for more info. By
-default the `hello_world_test.go` file is used.
+Like with |'g:go_template_file'|, this specifies the file to use for test
+tempaltes. The template file should be under the `templates` folder,
+alternatively absolute paths can be used, too. Checkout
+|'g:go_template_autocreate'| for more info. By default, the
+`hello_world_test.go` file is used.
>
let g:go_template_test_file = "hello_world_test.go"
<
@@ -1733,8 +1978,8 @@ i.e: |go#statusline#Show()|. By default it's enabled
<
*'g:go_echo_go_info'*
-Use this option to show the identifier information when completion is done. By
-default it's enabled >
+Use this option to show the identifier information when code completion is
+done. By default it's enabled. >
let g:go_echo_go_info = 1
<
@@ -1751,24 +1996,38 @@ default it's 60 seconds. Must be in milliseconds.
Sets the `transform` option for `gomodifytags` when using |:GoAddTags| or if
it's being used for snippet expansion of single fields. Possible options are:
-`snakecase`, `camelcase`. For the following case, if `snakecase` is used the
-field will be transformed to:
+`snakecase`, `camelcase`, `lispcase`, `pascalcase`, `keep`. For the following
+case, if `snakecase` is used the field will be transformed to:
>
type T struct {
- FooBarQuz string `json:"foo_bar_quz"
+ FooBarQuz string `json:"foo_bar_quz"`
}
<
If "camelcase" is used:
>
type T struct {
- FooBarQuz string `json:"fooBarQuz"
+ FooBarQuz string `json:"fooBarQuz"`
}
<
By default "snakecase" is used. Current values are: ["snakecase",
-"camelcase"].
+"camelcase", "lispcase", "pascalcase", "keep"].
>
let g:go_addtags_transform = 'snakecase'
+<
+ *'g:go_addtags_skip_unexported'*
+
+Sets the `skip-unexported` option for `gomodifytags` when using |:GoAddTags|.
+If set it will prevent `gomodifytags` from adding tags to unexported fields:
+>
+ type T struct {
+ FooBar string `json:"foo_bar"`
+ quz string
+ }
+<
+By default it is disabled.
+>
+ let g:go_addtags_skip_unexported = 0
<
*'g:go_debug'*
@@ -1780,7 +2039,10 @@ Currently accepted values:
debugger-state Expose debugger state in 'g:go_debug_diag'.
debugger-commands Echo communication between vim-go and `dlv`; requests and
responses are recorded in `g:go_debug_commands`.
- lsp Record lsp requests and responses in g:go_lsp_log.
+ lsp Echo communication between vim-go and `gopls`. All
+ communication is shown in a dedicated window. When
+ enabled before gopls is started, |:GoLSPDebugBrowser| can
+ be used to open a browser window to help debug gopls.
>
let g:go_debug = []
<
@@ -1924,6 +2186,18 @@ Highlight variable names in variable assignments (`x` in `x =`).
>
let g:go_highlight_variable_assignments = 0
<
+ *'g:go_highlight_diagnostic_errors'*
+
+Highlight diagnostic errors.
+>
+ let g:go_highlight_diagnostic_errors = 1
+<
+ *'g:go_highlight_diagnostic_warnings'*
+
+Highlight diagnostic warnings.
+>
+ let g:go_highlight_diagnostic_warnings = 1
+<
==============================================================================
*gohtmltmpl* *ft-gohtmltmpl-syntax*
@@ -1991,6 +2265,8 @@ The program will halt on the breakpoint, at which point you can inspect the
program state. You can go to the next line with |:GoDebugNext| () or step
in with |:GoDebugStep| ().
+The program can also be halted with `:GoDebugHalt` ().
+
The variable window in the bottom left (`GODEBUG_VARIABLES`) will display all
local variables. Struct values are displayed as `{...}`, array/slices as
`[4]`. Use on the variable name to expand the values.
@@ -2007,8 +2283,21 @@ the `dlv` process, or |:GoDebugRestart| to recompile the code.
*go-debug-commands*
DEBUGGER COMMANDS~
-Only |:GoDebugStart| and |:GoDebugBreakpoint| are available by default; the
-rest of the commands and mappings become available after starting debug mode.
+Only |:GoDebugAttach|, |:GoDebugStart|, |:GoDebugTest|, and
+|:GoDebugBreakpoint| are available by default. |:GoDebugContinue| becomes
+available after running |:GoDebugAttach|, |:GoDebugStart| or |:GoDebugTest|.
+The rest of the commands and mappings become available after executing
+|:GoDebugContinue|.
+
+ *:GoDebugAttach*
+:GoDebugAttach pid
+
+ Start the debug mode for pid; this does several things:
+
+ * Setup the debug windows according to |'g:go_debug_windows'|.
+ * Make the `:GoDebug*` commands and `(go-debug-*)` mappings available.
+
+ Use |:GoDebugStop| to stop `dlv` and exit debugging mode.
*:GoDebugStart*
:GoDebugStart [pkg] [program-args]
@@ -2018,8 +2307,8 @@ rest of the commands and mappings become available after starting debug mode.
* Setup the debug windows according to |'g:go_debug_windows'|.
* Make the `:GoDebug*` commands and `(go-debug-*)` mappings available.
- The current directory is used if [pkg] is empty. Any other arguments will
- be passed to the program.
+ The directory of the current buffer is used if [pkg] is empty. Any other
+ arguments will be passed to the program.
Use |:GoDebugStop| to stop `dlv` and exit debugging mode.
@@ -2032,13 +2321,20 @@ rest of the commands and mappings become available after starting debug mode.
Use `-test.flag` to pass flags to `go test` when debugging a test; for
example `-test.v` or `-test.run TestFoo`
-
*:GoDebugRestart*
:GoDebugRestart
Stop the program (if running) and restart `dlv` to recompile the package.
The current window layout and breakpoints will be left intact.
+ *:GoDebugHalt*
+ *(go-debug-halt)*
+:GoDebugHalt
+
+ Halt the program.
+
+ Mapped to by default.
+
*:GoDebugStop*
*(go-debug-stop)*
:GoDebugStop
@@ -2076,7 +2372,6 @@ rest of the commands and mappings become available after starting debug mode.
Advance execution by one line, also called "step over" by some other
debuggers.
- It will behave as |:GoDebugContinue| if the program isn't started.
Mapped to by default.
@@ -2086,7 +2381,6 @@ rest of the commands and mappings become available after starting debug mode.
Advance execution by one step, stopping at the next line of code that will
be executed (regardless of location).
- It will behave as |:GoDebugContinue| if the program isn't started.
Mapped to by default.
@@ -2097,7 +2391,6 @@ rest of the commands and mappings become available after starting debug mode.
Run all the code in the current function and halt when the function
returns ("step out of the current function").
- It will behave as |:GoDebugContinue| if the program isn't started.
*:GoDebugSet*
:GoDebugSet {var} {value}
@@ -2127,17 +2420,20 @@ DEBUGGER SETTINGS~
*'g:go_debug_windows'*
-Controls the window layout for debugging mode. This is a |dict| with three
-possible keys: "stack", "out", and "vars"; the windows will created in that
-order with the commands in the value.
+Controls the window layout for debugging mode. This is a |dict| with four
+possible keys: "vars", "stack", "goroutines", and "out"; each of the new
+windows will be created in that that order with the commands in the value. The
+current window is made the only window before creating the debug windows.
+
A window will not be created if a key is missing or empty.
Defaults:
>
let 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',
\ }
<
Show only variables on the right-hand side: >
@@ -2156,12 +2452,11 @@ Defaults to `127.0.0.1:8181`:
*'g:go_debug_log_output'*
-Specifies log output options for `dlv`. Value should be a single string
-of comma-separated options suitable for passing to `dlv`. An empty string
-(`''`) will suppress logging entirely.
-Default: `'debugger, rpc'`:
+Specifies log output options for `dlv`. Value should be a single string of
+comma-separated options suitable for passing to `dlv`. An empty string (`''`)
+will suppress logging entirely. Default: `'debugger,rpc'`:
>
- let g:go_debug_log = 'debugger, rpc'
+ let g:go_debug_log_output = 'debugger,rpc'
<
*'g:go_highlight_debug'*
@@ -2172,9 +2467,63 @@ Highlight the current line and breakpoints in the debugger.
let g:go_highlight_debug = 1
<
+ *'go:go_debug_breakpoint_sign_text'*
+
+Set the sign text used for breakpoints in the debugger. By default it's '>'.
+
+>
+ let g:go_debug_breakpoint_sign_text = '>'
+<
+
==============================================================================
FAQ TROUBLESHOOTING *go-troubleshooting*
+How do I troubleshoot problems?~
+
+One of the best ways to understand what vim-go is doing and the output from
+the tools to which it delegates is to use leverage the features described in
+|'g:go_debug'|.
+
+Completion and other functions that use `gopls` don't work~
+
+Vim-go is heavily reliant on `gopls` for completion and other functionality.
+Many of the features that use `gopls` (e.g. completion, jumping to
+definitions, showing identifier information, et al.) can be configured to
+delegate to other tools. e.g. completion via 'omnifunc', |'g:go_info_mode'|
+and |'g:go_def_mode'| can be set to use other tools for now (though some of
+the alternatives to `gopls` are effectively at their end of life and support
+for them from within vim-go may be removed soon).
+
+I want to disable `gopls`~
+
+Vim-go's use of `gopls` can be disabled with 'g:go_gopls_enabled'.
+
+Some users want to do this to limit the load on their system when using vim-go
+concurrently with an LSP client like vim-lsp. Instead of disabling vim-go's
+use of `gopls`, you may prefer to configure vim-go to share the `gopls`
+instance with other LSP plugins. 'g:go_gopls_options' can be used to configure
+how vim-go starts `gopls` so that the instance can be shared with other
+plugins and vim-go user's can leverage the full power of vim-go.
+
+I get a "Unknown function: go#config#..." error~
+
+This often happens to vim-polyglot users when new config options are added to
+vim-go. Run vim-polyglot's `build` script or make sure that vim-go is loaded
+before vim-polyglot.
+
+It can also happen when multiple versions of vim-go are installed and the
+version loaded by Vim doesn't have a function introduced by a later version.
+To see where vim-go is being loaded from run
+>
+ :verbose function go#config#FmtAutosave
+<
+
+The output will show the path to the `autoload/go/config.vim` that was loaded
+by Vim. Make sure the root of the path to output by the command is the path
+from which vim-go is expected to sourced. If it is not rooted as expected,
+then there are multiple copies of vim-go installed; remove the unexpected
+copies.
+
I get "not an editor command" error when I invoke :GoXXX~
This happens if vim-go is not installed properly. Be sure you have added this
@@ -2289,9 +2638,9 @@ while saving and opening files. The following fixes this:
let g:syntastic_go_checkers = ['golint', 'govet']
let g:syntastic_mode_map = { 'mode': 'active', 'passive_filetypes': ['go'] }
<
-If you want to add errcheck you can use gometalinter as a wrapper
+If you want to add errcheck you can use golangci-lint as a wrapper:
>
- let g:syntastic_go_checkers = ['golint', 'govet', 'gometalinter']
+ let g:syntastic_go_checkers = ['golint', 'govet', 'golangci-lint']
let g:syntastic_go_gometalinter_args = ['--disable-all', '--enable=errcheck']
let g:syntastic_mode_map = { 'mode': 'active', 'passive_filetypes': ['go'] }
<
@@ -2365,7 +2714,7 @@ To run tests vim-go comes with three small helper scripts:
`scripts/test` Run all tests with a Vim from `/tmp/vim-go-test/`.
All scripts accept a Vim version as the first argument, which can be
-`vim-7.4`, `vim-8.0`, or `nvim`. You will need to install a Vim version with
+`vim-8.0` or `nvim`. You will need to install a Vim version with
`install-vim` before you can use `run-vim` or `test`.
You can install and test all Vim versions by running `make`.
diff --git a/pack/acp/start/vim-go/ftplugin/go.vim b/pack/acp/start/vim-go/ftplugin/go.vim
index 233a47a..687f881 100644
--- a/pack/acp/start/vim-go/ftplugin/go.vim
+++ b/pack/acp/start/vim-go/ftplugin/go.vim
@@ -13,8 +13,8 @@ let b:did_ftplugin = 1
let s:cpo_save = &cpo
set cpo&vim
-let b:undo_ftplugin = "setl fo< com< cms<
- \ | exe 'au! vim-go-buffer * '"
+let b:undo_ftplugin = "setl fo< com< cms<"
+ \ . "| exe 'au! vim-go-buffer * '"
setlocal formatoptions-=t
@@ -25,10 +25,9 @@ setlocal noexpandtab
compiler go
-" Set autocompletion
-setlocal omnifunc=go#complete#Complete
-if !go#util#has_job()
- setlocal omnifunc=go#complete#GocodeComplete
+if go#config#CodeCompletionEnabled()
+ " Set autocompletion
+ setlocal omnifunc=go#complete#Complete
endif
if get(g:, "go_doc_keywordprg_enabled", 1)
@@ -72,43 +71,59 @@ if get(g:, "go_textobj_enabled", 1)
xnoremap [[ :call go#textobj#FunctionJump('v', 'prev')
endif
-if go#config#AutoTypeInfo() || go#config#AutoSameids()
- let &l:updatetime= get(g:, "go_updatetime", 800)
-endif
-
" Autocommands
" ============================================================================
"
augroup vim-go-buffer
autocmd! *
- " The file is registered (textDocument/DidOpen) with gopls in in
- " plugin/go.vim on the FileType event.
- " TODO(bc): handle all the other events that may be of interest to gopls,
- " too (e.g. BufFilePost , CursorHold , CursorHoldI, FileReadPost,
- " StdinReadPre, BufWritePost, TextChange, TextChangedI)
+ " The file is registered (textDocument/DidOpen) with gopls in plugin/go.vim
+ " on the FileType event.
+
if go#util#has_job()
- autocmd BufWritePost call go#lsp#DidChange(expand(':p'))
- autocmd FileChangedShell call go#lsp#DidChange(expand(':p'))
+ autocmd BufWritePost,FileChangedShellPost call go#lsp#DidChange(expand(':p'))
autocmd BufDelete call go#lsp#DidClose(expand(':p'))
endif
- autocmd CursorHold call go#auto#auto_type_info()
- autocmd CursorHold call go#auto#auto_sameids()
+ " send the textDocument/didChange notification when idle. go#lsp#DidChange
+ " will not send an event if the buffer hasn't changed since the last
+ " notification.
+ autocmd CursorHold,CursorHoldI call go#lsp#DidChange(expand(':p'))
+
+ autocmd BufEnter,CursorHold call go#auto#update_autocmd()
" Echo the identifier information when completion is done. Useful to see
" the signature of a function, etc...
if exists('##CompleteDone')
- autocmd CompleteDone call go#auto#echo_go_info()
+ autocmd CompleteDone call go#auto#complete_done()
endif
autocmd BufWritePre call go#auto#fmt_autosave()
autocmd BufWritePost call go#auto#metalinter_autosave()
- " clear SameIds when the buffer is unloaded so that loading another buffer
- " in the same window doesn't highlight the most recently matched
- " identifier's positions.
- autocmd BufWinEnter call go#guru#ClearSameIds()
+ if !has('textprop')
+ "TODO(bc): how to clear sameids and diagnostics when a non-go buffer is
+ " loaded into a window and the previously loaded buffer is still loaded in
+ " 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 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 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 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 call go#lsp#ClearDiagnosticHighlights()
+ endif
autocmd BufEnter
\ if go#config#AutodetectGopath() && !exists('b:old_gopath')
diff --git a/pack/acp/start/vim-go/ftplugin/go/commands.vim b/pack/acp/start/vim-go/ftplugin/go/commands.vim
index 726b944..ebc911b 100644
--- a/pack/acp/start/vim-go/ftplugin/go/commands.vim
+++ b/pack/acp/start/vim-go/ftplugin/go/commands.vim
@@ -3,7 +3,7 @@ command! -nargs=? -complete=customlist,go#rename#Complete GoRename call go#renam
" -- guru
command! -nargs=* -complete=customlist,go#package#Complete GoGuruScope call go#guru#Scope()
-command! -range=% GoImplements call go#guru#Implements()
+command! -range=% GoImplements call go#implements#Implements()
command! -range=% GoPointsTo call go#guru#PointsTo()
command! -range=% GoWhicherrs call go#guru#Whicherrs()
command! -range=% GoCallees call go#guru#Callees()
@@ -12,7 +12,7 @@ command! -range=% GoCallers call go#guru#Callers()
command! -range=% GoCallstack call go#guru#Callstack()
command! -range=% GoFreevars call go#guru#Freevars()
command! -range=% GoChannelPeers call go#guru#ChannelPeers()
-command! -range=% GoReferrers call go#guru#Referrers()
+command! -range=% GoReferrers call go#referrers#Referrers()
command! -range=0 GoSameIds call go#guru#SameIds(1)
command! -range=0 GoSameIdsClear call go#guru#ClearSameIds()
@@ -105,8 +105,9 @@ command! -nargs=0 GoFillStruct call go#fillstruct#FillStruct()
" -- debug
if !exists(':GoDebugStart')
- command! -nargs=* -complete=customlist,go#package#Complete GoDebugStart call go#debug#Start(0, )
- command! -nargs=* -complete=customlist,go#package#Complete GoDebugTest call go#debug#Start(1, )
+ command! -nargs=* -complete=customlist,go#package#Complete GoDebugStart call go#debug#Start('debug', )
+ command! -nargs=* -complete=customlist,go#package#Complete GoDebugTest call go#debug#Start('test', )
+ command! -nargs=1 GoDebugAttach call go#debug#Start('attach', )
command! -nargs=? GoDebugBreakpoint call go#debug#Breakpoint()
endif
@@ -116,4 +117,12 @@ command! -nargs=0 GoReportGitHubIssue call go#issue#New()
" -- iferr
command! -nargs=0 GoIfErr call go#iferr#Generate()
+" -- lsp
+command! -nargs=+ -complete=dir GoAddWorkspace call go#lsp#AddWorkspaceDirectory()
+command! -nargs=0 GoLSPDebugBrowser call go#lsp#DebugBrowser()
+command! -nargs=* -bang GoDiagnostics call go#lint#Diagnostics(0, )
+
+" -- term
+command! GoToggleTermCloseOnExit call go#term#ToggleCloseOnExit()
+
" vim: sw=2 ts=2 et
diff --git a/pack/acp/start/vim-go/ftplugin/go/mappings.vim b/pack/acp/start/vim-go/ftplugin/go/mappings.vim
index f1f069e..16e4db6 100644
--- a/pack/acp/start/vim-go/ftplugin/go/mappings.vim
+++ b/pack/acp/start/vim-go/ftplugin/go/mappings.vim
@@ -11,7 +11,7 @@ endif
" Some handy plug mappings
nnoremap (go-run) :call go#cmd#Run(!g:go_jump_to_error)
-if has("nvim")
+if has("nvim") || has("terminal")
nnoremap (go-run-vertical) :call go#cmd#RunTerm(!g:go_jump_to_error, 'vsplit', [])
nnoremap (go-run-split) :call go#cmd#RunTerm(!g:go_jump_to_error, 'split', [])
nnoremap (go-run-tab) :call go#cmd#RunTerm(!g:go_jump_to_error, 'tabe', [])
@@ -35,14 +35,14 @@ nnoremap (go-info) :call go#tool#Info(1)
nnoremap (go-import) :call go#import#SwitchImport(1, '', expand(''), '')
nnoremap (go-imports) :call go#fmt#Format(1)
-nnoremap