+16
.editorconfig
+16
.editorconfig
···
1
+
root = true
2
+
3
+
[*]
4
+
indent_style = space
5
+
indent_size = 4
6
+
end_of_line = lf
7
+
insert_final_newline = true
8
+
trim_trailing_whitespace = true
9
+
charset = utf-8
10
+
11
+
[*.{md,yml,yaml,toml,lua,vim}]
12
+
indent_size = 2
13
+
14
+
[*.go]
15
+
indent_style = tab
16
+
indent_size = 4
+4
.envrc
+4
.envrc
-23
.github/workflows/ci.yml
-23
.github/workflows/ci.yml
···
1
-
name: Format and lint
2
-
on: [push, pull_request]
3
-
4
-
jobs:
5
-
format:
6
-
name: stylua
7
-
runs-on: ubuntu-latest
8
-
steps:
9
-
- uses: actions/checkout@v2
10
-
- uses: JohnnyMorganz/stylua-action@1.0.0
11
-
with:
12
-
token: ${{ secrets.GITHUB_TOKEN }}
13
-
args: --check .
14
-
15
-
lint:
16
-
name: selene
17
-
runs-on: ubuntu-latest
18
-
steps:
19
-
- uses: actions/checkout@v2
20
-
- uses: NTBBloodbath/selene-action@v1.0.0
21
-
with:
22
-
token: ${{ secrets.GITHUB_TOKEN }}
23
-
args: --display-style=quiet .
+58
.github/workflows/linters.yml
+58
.github/workflows/linters.yml
···
1
+
name: linters
2
+
3
+
on:
4
+
push:
5
+
branches:
6
+
- main
7
+
- develop
8
+
pull_request:
9
+
10
+
jobs:
11
+
linters:
12
+
name: Lua
13
+
runs-on: ubuntu-latest
14
+
steps:
15
+
- uses: actions/checkout@v4
16
+
- uses: JohnnyMorganz/stylua-action@v3
17
+
with:
18
+
token: ${{ secrets.GITHUB_TOKEN }}
19
+
version: latest
20
+
args: --check .
21
+
22
+
- uses: NTBBloodbath/selene-action@v1.0.0
23
+
with:
24
+
token: ${{ secrets.GITHUB_TOKEN }}
25
+
args: .
26
+
27
+
docs:
28
+
name: Docs
29
+
runs-on: ubuntu-latest
30
+
steps:
31
+
- uses: actions/checkout@v4
32
+
33
+
- name: Install Task
34
+
uses: arduino/setup-task@v1
35
+
with:
36
+
version: 3.x
37
+
repo-token: ${{ secrets.GITHUB_TOKEN }}
38
+
39
+
- name: Install NeoVim
40
+
uses: rhysd/action-setup-vim@v1
41
+
with:
42
+
neovim: true
43
+
version: stable
44
+
45
+
- name: Cache .tests
46
+
uses: actions/cache@v4
47
+
with:
48
+
path: |
49
+
${{ github.workspace }}/.tests
50
+
key: ${{ runner.os }}-tests-${{ hashFiles('${{ github.workspace }}/.tests') }}
51
+
52
+
- name: Generate docs
53
+
run: task docgen
54
+
55
+
- name: Diff
56
+
run: |
57
+
git diff doc
58
+
exit $(git status --porcelain doc | wc -l | tr -d " ")
+62
.github/workflows/tests.yml
+62
.github/workflows/tests.yml
···
1
+
name: tests
2
+
3
+
on:
4
+
push:
5
+
branches:
6
+
- main
7
+
- develop
8
+
pull_request:
9
+
10
+
jobs:
11
+
tests:
12
+
strategy:
13
+
matrix:
14
+
os: [ubuntu-latest]
15
+
version:
16
+
- stable
17
+
- nightly
18
+
- v0.10.0
19
+
- v0.10.4
20
+
- v0.11.0
21
+
- v0.11.1
22
+
- v0.11.2
23
+
- v0.11.3
24
+
- v0.11.4
25
+
runs-on: ${{ matrix.os }}
26
+
steps:
27
+
- name: Install Task
28
+
uses: arduino/setup-task@v1
29
+
with:
30
+
version: 3.x
31
+
repo-token: ${{ secrets.GITHUB_TOKEN }}
32
+
33
+
- name: Install Go
34
+
uses: actions/setup-go@v5
35
+
with:
36
+
go-version: "1.24.0"
37
+
check-latest: false
38
+
39
+
- name: Install NeoVim
40
+
uses: rhysd/action-setup-vim@v1
41
+
with:
42
+
neovim: true
43
+
version: ${{ matrix.version }}
44
+
45
+
- uses: actions/checkout@v4
46
+
47
+
- name: Cache .tests
48
+
uses: actions/cache@v4
49
+
with:
50
+
path: |
51
+
${{ github.workspace }}/.tests
52
+
~/.cache/go-build
53
+
~/go/pkg/mod
54
+
key: ${{ runner.os }}-tests-${{ hashFiles('${{ github.workspace }}/.tests') }}
55
+
56
+
- name: Install Go bins
57
+
run: task install-deps
58
+
59
+
- name: Run Tests
60
+
run: |
61
+
nvim --version
62
+
task test
-15
.pre-commit-config.yaml
-15
.pre-commit-config.yaml
+49
CONTRIBUTING.md
+49
CONTRIBUTING.md
···
1
+
# Contributing to `gopher.nvim`
2
+
Thank you for taking the time to submit some code to gopher.nvim. It means a lot!
3
+
4
+
### Task running
5
+
In this codebase for running tasks is used [Taskfile](https://taskfile.dev).
6
+
You can install it with:
7
+
```bash
8
+
go install github.com/go-task/task/v3/cmd/task@latest
9
+
```
10
+
11
+
### Formatting and linting
12
+
Code is formatted by [stylua](https://github.com/JohnnyMorganz/StyLua) and linted using [selene](https://github.com/Kampfkarren/selene).
13
+
You can install these with:
14
+
15
+
```bash
16
+
sudo pacman -S selene stylua
17
+
# or whatever is your package manager
18
+
```
19
+
20
+
For formatting use this following commands, or setup your editor to integrate with selene/stylua:
21
+
```bash
22
+
task format
23
+
task lint
24
+
```
25
+
26
+
### Documentation
27
+
Here we're using [mini.doc](https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-doc.md)
28
+
for generating vimhelp files based on [LuaCats](https://luals.github.io/wiki/annotations/) annotations in comments.
29
+
30
+
For demo gifs in [readme](./README.md) we're using [vhs](https://github.com/charmbracelet/vhs).
31
+
All files related to demos live in [/vhs](./vhs) dir.
32
+
33
+
You can generate docs with:
34
+
```bash
35
+
task docgen # generates vimhelp
36
+
task vhs:generate # generates demo gifs
37
+
```
38
+
39
+
### Commit messages
40
+
We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), please follow it.
41
+
42
+
### Testing
43
+
For testing this plugins uses [mini.test](https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-test.md).
44
+
All tests live in [/spec](./spec) dir.
45
+
46
+
You can run tests with:
47
+
```bash
48
+
task test
49
+
```
+21
LICENSE
+21
LICENSE
···
1
+
MIT License
2
+
3
+
Copyright (c) 2025 Oleksandr Smirnov
4
+
5
+
Permission is hereby granted, free of charge, to any person obtaining a copy
6
+
of this software and associated documentation files (the "Software"), to deal
7
+
in the Software without restriction, including without limitation the rights
8
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+
copies of the Software, and to permit persons to whom the Software is
10
+
furnished to do so, subject to the following conditions:
11
+
12
+
The above copyright notice and this permission notice shall be included in all
13
+
copies or substantial portions of the Software.
14
+
15
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+
SOFTWARE.
-11
Makefile
-11
Makefile
+244
-89
README.md
+244
-89
README.md
···
1
1
# gopher.nvim
2
2
3
+
[](https://stand-with-ukraine.pp.ua)
4
+
3
5
Minimalistic plugin for Go development in Neovim written in Lua.
4
6
5
-
It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim.
7
+
It's **NOT** an LSP tool, the goal of this plugin is to add go tooling support in Neovim.
8
+
9
+
> All development of new and maybe undocumented, and unstable features is happening on [develop](https://github.com/olexsmir/gopher.nvim/tree/develop) branch.
10
+
11
+
## Table of content
12
+
* [How to install](#install-using-lazynvim)
13
+
* [Features](#features)
14
+
* [Configuration](#configuration)
15
+
* [Troubleshooting](#troubleshooting)
16
+
* [Contributing](#contributing)
6
17
7
-
## Install
18
+
## Install (using [lazy.nvim](https://github.com/folke/lazy.nvim))
8
19
9
-
Pre-dependency: [go](https://github.com/golang/go) (tested on 1.17 and 1.18)
20
+
Requirements:
21
+
22
+
- **Neovim 0.10** or later
23
+
- Treesitter parser for `go`(`:TSInstall go` if you use [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter))
24
+
- [Go](https://github.com/golang/go) installed
25
+
26
+
> [!IMPORTANT]
27
+
> If you prefer using other forges, this repository is also mirrored at:
28
+
> - [tangled.org](https://tangled.org): [`https://tangled.org/olexsmir.xyz/gopher.nvim`](https://tangled.org/olexsmir.xyz/gopher.nvim)
29
+
> - [codeberg.org](https://codeberg.org): [`https://codeberg.org/olexsmir/gopher.nvim`](https://codeberg.org/olexsmir/gopher.nvim)
10
30
11
31
```lua
12
-
use {
32
+
-- NOTE: this plugin is already lazy-loaded and adds only about 1ms
33
+
-- of load time to your config
34
+
{
13
35
"olexsmir/gopher.nvim",
14
-
requires = { -- dependencies
15
-
"nvim-lua/plenary.nvim",
16
-
"nvim-treesitter/nvim-treesitter",
17
-
},
36
+
ft = "go",
37
+
-- branch = "develop"
38
+
-- (optional) updates the plugin's dependencies on each update
39
+
build = function()
40
+
vim.cmd.GoInstallDeps()
41
+
end,
42
+
---@module "gopher"
43
+
---@type gopher.Config
44
+
opts = {},
18
45
}
19
46
```
20
47
21
-
Also, run `TSInstall go` if `go` parser if isn't installed yet.
48
+
## Features
49
+
50
+
<details>
51
+
<summary>
52
+
<b>Install plugin's go deps</b>
53
+
</summary>
54
+
55
+
```vim
56
+
:GoInstallDeps
57
+
```
58
+
59
+
This will install the following tools:
60
+
61
+
- [gomodifytags](https://github.com/fatih/gomodifytags)
62
+
- [impl](https://github.com/josharian/impl)
63
+
- [gotests](https://github.com/cweill/gotests)
64
+
- [iferr](https://github.com/koron/iferr)
65
+
- [json2go](https://github.com/olexsmir/json2go)
66
+
</details>
67
+
68
+
<details>
69
+
<summary>
70
+
<b>Add and remove tags for structs via <a href="https://github.com/fatih/gomodifytags">gomodifytags</a></b>
71
+
</summary>
72
+
73
+

74
+
75
+
By default `json` tag will be added/removed, if not set:
76
+
77
+
```vim
78
+
" add json tag
79
+
:GoTagAdd json
80
+
81
+
" add json tag with omitempty option
82
+
:GoTagAdd json=omitempty
83
+
84
+
" remove yaml tag
85
+
:GoTagRm yaml
86
+
```
87
+
88
+
```lua
89
+
-- or you can use lua api
90
+
require("gopher").tags.add "xml"
91
+
require("gopher").tags.rm "proto"
92
+
```
93
+
</details>
94
+
95
+
<details>
96
+
<summary>
97
+
<b>Generating tests via <a href="https://github.com/cweill/gotests">gotests</a></b>
98
+
</summary>
99
+
100
+
```vim
101
+
" Generate one test for a specific function/method(one under cursor)
102
+
:GoTestAdd
103
+
104
+
" Generate all tests for all functions/methods in the current file
105
+
:GoTestsAll
106
+
107
+
" Generate tests for only exported functions/methods in the current file
108
+
:GoTestsExp
109
+
```
110
+
111
+
```lua
112
+
-- or you can use lua api
113
+
require("gopher").test.add()
114
+
require("gopher").test.exported()
115
+
require("gopher").test.all()
116
+
```
117
+
118
+
For named tests see `:h gopher.nvim-gotests-named`
119
+
</details>
120
+
121
+
<details>
122
+
<summary>
123
+
<b>Run commands like <code>go mod/get/etc</code> inside of nvim</b>
124
+
</summary>
125
+
126
+
```vim
127
+
:GoGet github.com/gorilla/mux
128
+
129
+
" Link can have an `http` or `https` prefix.
130
+
:GoGet https://github.com/lib/pq
22
131
23
-
## Config
132
+
" You can provide more than one package url
133
+
:GoGet github.com/jackc/pgx/v5 github.com/google/uuid/
24
134
25
-
By `.setup` function you can configure the plugin.
135
+
" go mod commands
136
+
:GoMod tidy
137
+
:GoMod init new-shiny-project
26
138
27
-
Note:
139
+
" go work commands
140
+
:GoWork sync
28
141
29
-
- Installer does not install the tool in user set path
142
+
" run go generate in cwd
143
+
:GoGenerate
30
144
31
-
```lua
32
-
require("gopher").setup {
33
-
commands = {
34
-
go = "go",
35
-
gomodifytags = "gomodifytags",
36
-
gotests = "~/go/bin/gotests", -- also you can set custom command path
37
-
impl = "impl",
38
-
},
39
-
}
40
-
```
145
+
" run go generate for the current file
146
+
:GoGenerate %
147
+
```
148
+
</details>
41
149
42
-
## Features
150
+
<details>
151
+
<summary>
152
+
<b>Interface implementation via <a href="https://github.com/josharian/impl">impl<a></b>
153
+
</summary>
43
154
44
-
1. Installation requires this go tool:
155
+

45
156
46
-
```vim
47
-
:GoInstallDeps
48
-
```
157
+
Syntax of the command:
158
+
```vim
159
+
:GoImpl [receiver] [interface]
49
160
50
-
It will install next tools:
161
+
" also you can put a cursor on the struct and run
162
+
:GoImpl [interface]
163
+
```
51
164
52
-
- [gomodifytags](https://github.com/fatih/gomodifytags)
53
-
- [impl](https://github.com/josharian/impl)
54
-
- [gotests](https://github.com/cweill/gotests)
165
+
Usage examples:
166
+
```vim
167
+
:GoImpl r Read io.Reader
168
+
:GoImpl Write io.Writer
55
169
56
-
2. Modify struct tags:
57
-
By default `json` tag will be added/removed, if not set:
170
+
" or you can simply put a cursor on the struct and run
171
+
:GoImpl io.Reader
172
+
```
173
+
</details>
58
174
59
-
```vim
60
-
:GoTagAdd json " For add json tag
61
-
:GoTagRm yaml " For remove yaml tag
62
-
```
175
+
<details>
176
+
<summary>
177
+
<b>Generate boilerplate for doc comments</b>
178
+
</summary>
63
179
64
-
3. Run `go mod` command:
180
+

65
181
66
-
```vim
67
-
:GoMod tidy " Runs `go mod tidy`
68
-
:GoMod init asdf " Runs `go mod init asdf`
69
-
```
182
+
First set a cursor on **public** package/function/interface/struct and execute:
70
183
71
-
4. Run `go get` command
184
+
```vim
185
+
:GoCmt
186
+
```
187
+
</details>
72
188
73
-
Link can have a `http` or `https` prefix.
189
+
<details>
190
+
<summary>
191
+
<b>Convert json to Go types</b>
192
+
</summary>
74
193
75
-
You can provide more than one package url:
194
+

76
195
77
-
```vim
78
-
:GoGet github.com/gorilla/mux
79
-
```
196
+
`:GoJson` opens a temporary buffer where you can paste or write JSON.
197
+
Saving the buffer (`:w` or `:wq`) automatically closes it and inserts the generated Go struct into the original buffer at the cursor position.
80
198
81
-
5. Interface implementation
199
+
Alternatively, you can pass JSON directly as an argument:
200
+
```vim
201
+
:GoJson {"name": "Alice", "age": 30}
202
+
```
82
203
83
-
Command syntax:
204
+
Additionally, `gopher.json2go` provides lua api, see `:h gopher.nvim-json2go` for details.
205
+
</details>
84
206
85
-
```vim
86
-
:GoImpl [receiver] [interface]
87
207
88
-
" Also you can put cursor on the struct and run:
89
-
:GoImpl [interface]
90
-
```
208
+
<details>
209
+
<summary>
210
+
<b>Generate <code>if err != nil {</code> via <a href="https://github.com/koron/iferr">iferr</a></b>
211
+
</summary>
91
212
92
-
Example of usage:
213
+

93
214
94
-
```vim
95
-
" Example
96
-
:GoImpl r Read io.Reader
97
-
" or simply put your cursor in the struct and run:
98
-
:GoImpl io.Reader
99
-
```
215
+
Set the cursor on the line with `err` and execute
100
216
101
-
6. Generate tests with [gotests](https://github.com/cweill/gotests)
217
+
```vim
218
+
:GoIfErr
219
+
```
220
+
</details>
102
221
103
-
Generate one test for spesific function/method:
222
+
## Configuration
104
223
105
-
```vim
106
-
:GoTestAdd
107
-
```
224
+
> [!IMPORTANT]
225
+
>
226
+
> If you need more info look `:h gopher.nvim`
108
227
109
-
Generate all tests for all functions/methods in current file:
228
+
**Take a look at default options (might be a bit outdated, look `:h gopher.nvim-config`)**
110
229
111
-
```vim
112
-
:GoTestsAll
113
-
```
230
+
```lua
231
+
require("gopher").setup {
232
+
-- log level, you might consider using DEBUG or TRACE for debugging the plugin
233
+
log_level = vim.log.levels.INFO,
114
234
115
-
Generate tests only for exported functions/methods in current file:
235
+
-- timeout for running internal commands
236
+
timeout = 2000,
116
237
117
-
```vim
118
-
:GoTestsExp
119
-
```
238
+
-- timeout for running installer commands(e.g :GoDepsInstall, :GoDepsInstallSync)
239
+
installer_timeout = 999999,
120
240
121
-
7. Run `go generate` command;
241
+
-- user specified paths to binaries
242
+
commands = {
243
+
go = "go",
244
+
gomodifytags = "gomodifytags",
245
+
gotests = "gotests",
246
+
impl = "impl",
247
+
iferr = "iferr",
248
+
},
249
+
gotests = {
250
+
-- a default template that gotess will use.
251
+
-- gotets doesn't have template named `default`, we use it to represent absence of the provided template.
252
+
template = "default",
122
253
123
-
```vim
124
-
" Run `go generate` in cwd path
125
-
:GoGenerate
254
+
-- path to a directory containing custom test code templates
255
+
template_dir = nil,
126
256
127
-
" Run `go generate` for current file
128
-
:GoGenerate %
129
-
```
257
+
-- use named tests(map with test name as key) in table tests(slice of structs by default)
258
+
named = false,
259
+
},
260
+
gotag = {
261
+
transform = "snakecase",
130
262
131
-
8. Generate doc comment
263
+
-- default tags to add to struct fields
264
+
default_tag = "json",
132
265
133
-
First set a cursor on **public** package/function/interface/struct and execure:
266
+
-- default tag option added struct fields, set to nil to disable
267
+
-- e.g: `option = "json=omitempty,xml=omitempty`
268
+
option = nil,
269
+
},
270
+
iferr = {
271
+
-- choose a custom error message, nil to use default
272
+
-- e.g: `message = 'fmt.Errorf("failed to %w", err)'`
273
+
message = nil,
274
+
},
275
+
json2go = {
276
+
-- command used to open interactive input.
277
+
-- e.g: `split`, `botright split`, `tabnew`
278
+
interactive_cmd = "vsplit",
134
279
135
-
```vim
136
-
:GoCmt
280
+
-- name of autogenerated struct
281
+
-- e.g: "MySuperCoolName"
282
+
type_name = nil,
283
+
},
284
+
}
137
285
```
138
286
139
-
## Thanks:
287
+
## Troubleshooting
288
+
The most common issue with the plugin is missing dependencies.
289
+
Run `:checkhealth gopher` to verify that the plugin is installed correctly.
290
+
If any binaries are missing, install them using `:GoInstallDeps`.
140
291
141
-
- [go.nvim](https://github.com/ray-x/go.nvim)
292
+
If the issue persists, feel free to [open a new issue](https://github.com/olexsmir/gopher.nvim/issues/new).
293
+
294
+
## Contributing
295
+
296
+
PRs are always welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md)
+42
Taskfile.yml
+42
Taskfile.yml
···
1
+
version: "3"
2
+
3
+
includes:
4
+
vhs:
5
+
taskfile: ./vhs/Taskfile.yml
6
+
dir: ./vhs
7
+
8
+
tasks:
9
+
lint:
10
+
cmds:
11
+
- selene .
12
+
- stylua --check .
13
+
14
+
format:
15
+
cmd: stylua .
16
+
17
+
test:
18
+
cmds:
19
+
- |
20
+
nvim --clean --headless \
21
+
-u ./scripts/minimal_init.lua \
22
+
-c "lua MiniTest.run()" \
23
+
-c ":qa!"
24
+
nvim:
25
+
cmd: nvim --clean -u "./scripts/minimal_init.lua" {{ .CLI_ARGS }}
26
+
27
+
docgen:
28
+
desc: generate vimhelp
29
+
cmds:
30
+
- |
31
+
nvim --clean --headless \
32
+
-u "./scripts/minimal_init.lua" \
33
+
-c "luafile ./scripts/docgen.lua" \
34
+
-c ":qa!"
35
+
36
+
install-deps:
37
+
desc: installs go bin (used in CI)
38
+
cmds:
39
+
- |
40
+
nvim --clean --headless \
41
+
-u "./scripts/minimal_init.lua" \
42
+
+GoInstallDepsSync +qa
-3
autoload/health/gopher.vim
-3
autoload/health/gopher.vim
+1
doc/.gitignore
+1
doc/.gitignore
···
1
+
/tags
+294
doc/gopher.nvim.txt
+294
doc/gopher.nvim.txt
···
1
+
*gopher.nvim* Enhance your golang experience
2
+
3
+
MIT License Copyright (c) 2025 Oleksandr Smirnov
4
+
5
+
==============================================================================
6
+
7
+
gopher.nvim is a minimalistic plugin for Go development in Neovim written in Lua.
8
+
It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim.
9
+
10
+
Table of Contents
11
+
Setup ................................................ |gopher.nvim-setup()|
12
+
Install dependencies ............................ |gopher.nvim-dependencies|
13
+
Config ................................................ |gopher.nvim-config|
14
+
Commands ............................................ |gopher.nvim-commands|
15
+
Modify struct tags ............................... |gopher.nvim-struct-tags|
16
+
json2go .............................................. |gopher.nvim-json2go|
17
+
Auto implementation of interface methods ................ |gopher.nvim-impl|
18
+
Generating unit tests boilerplate .................... |gopher.nvim-gotests|
19
+
Iferr .................................................. |gopher.nvim-iferr|
20
+
Generate comments ................................... |gopher.nvim-comments|
21
+
22
+
------------------------------------------------------------------------------
23
+
*gopher.nvim-setup()*
24
+
`gopher.setup`({user_config})
25
+
Setup function. This method simply merges default config with opts table.
26
+
You can read more about configuration at |gopher.nvim-config|
27
+
Calling this function is optional, if you ok with default settings.
28
+
See |gopher.nvim.config|
29
+
30
+
Usage ~
31
+
>lua
32
+
require("gopher").setup {} -- use default config or replace {} with your own
33
+
<
34
+
Parameters ~
35
+
{user_config} `(gopher.Config)` See |gopher.nvim-config|
36
+
37
+
------------------------------------------------------------------------------
38
+
*gopher.nvim-dependencies*
39
+
`gopher.install_deps`
40
+
41
+
Gopher.nvim implements most of its features using third-party tools. To
42
+
install plugin's dependencies, you can run:
43
+
`:GoInstallDeps` or `:GoInstallDepsSync`
44
+
or use `require("gopher").install_deps()` if you prefer lua api.
45
+
46
+
47
+
==============================================================================
48
+
------------------------------------------------------------------------------
49
+
*gopher.nvim-config*
50
+
`default_config`
51
+
>lua
52
+
local default_config = {
53
+
-- log level, you might consider using DEBUG or TRACE for debugging the plugin
54
+
---@type number
55
+
log_level = vim.log.levels.INFO,
56
+
57
+
-- timeout for running internal commands
58
+
---@type number
59
+
timeout = 2000,
60
+
61
+
-- timeout for running installer commands(e.g :GoDepsInstall, :GoDepsInstallSync)
62
+
---@type number
63
+
installer_timeout = 999999,
64
+
65
+
-- user specified paths to binaries
66
+
---@class gopher.ConfigCommand
67
+
commands = {
68
+
go = "go",
69
+
gomodifytags = "gomodifytags",
70
+
gotests = "gotests",
71
+
impl = "impl",
72
+
iferr = "iferr",
73
+
json2go = "json2go",
74
+
},
75
+
---@class gopher.ConfigGotests
76
+
gotests = {
77
+
-- a default template that gotess will use.
78
+
-- gotets doesn't have template named `default`, we use it to represent absence of the provided template.
79
+
template = "default",
80
+
81
+
-- path to a directory containing custom test code templates
82
+
---@type string|nil
83
+
template_dir = nil,
84
+
85
+
-- use named tests(map with test name as key) in table tests(slice of structs by default)
86
+
named = false,
87
+
},
88
+
---@class gopher.ConfigGoTag
89
+
gotag = {
90
+
---@type gopher.ConfigGoTagTransform
91
+
transform = "snakecase",
92
+
93
+
-- default tags to add to struct fields
94
+
default_tag = "json",
95
+
96
+
-- default tag option added struct fields, set to nil to disable
97
+
-- e.g: `option = "json=omitempty,xml=omitempty`
98
+
---@type string|nil
99
+
option = nil,
100
+
},
101
+
---@class gopher.ConfigIfErr
102
+
iferr = {
103
+
-- choose a custom error message, nil to use default
104
+
-- e.g: `message = 'fmt.Errorf("failed to %w", err)'`
105
+
---@type string|nil
106
+
message = nil,
107
+
},
108
+
---@class gopher.ConfigJson2Go
109
+
json2go = {
110
+
-- command used to open interactive input.
111
+
-- e.g: `split`, `botright split`, `tabnew`
112
+
interactive_cmd = "vsplit",
113
+
114
+
-- name of autogenerated struct, if nil none, will the default one of json2go.
115
+
-- e.g: "MySuperCoolName"
116
+
---@type string|nil
117
+
type_name = nil,
118
+
},
119
+
}
120
+
<
121
+
Class ~
122
+
{gopher.Config}
123
+
Fields ~
124
+
{setup} `(fun(user_config?: gopher.Config))`
125
+
126
+
127
+
==============================================================================
128
+
------------------------------------------------------------------------------
129
+
*gopher.nvim-commands*
130
+
131
+
If don't want to automatically register plugins' commands,
132
+
you can set `vim.g.gopher_register_commands` to `false`, before loading the plugin.
133
+
134
+
135
+
==============================================================================
136
+
------------------------------------------------------------------------------
137
+
*gopher.nvim-struct-tags*
138
+
139
+
`struct_tags` is utilizing the `gomodifytags` tool to add or remove tags to
140
+
struct fields.
141
+
142
+
Usage ~
143
+
144
+
How to add/remove/clear tags to struct fields:
145
+
1. Place cursor on the struct
146
+
2. Run `:GoTagAdd json` to add json tags to struct fields
147
+
3. Run `:GoTagRm json` to remove json tags to struct fields
148
+
4. Run `:GoTagClear` to clear all tags from struct fields
149
+
150
+
If you want to add/remove tag with options, you can use `json=omitempty`
151
+
(where json is tag, and omitempty is its option).
152
+
Example: `:GoTagAdd xml json=omitempty`
153
+
154
+
155
+
NOTE: if you dont specify the tag it will use `json` as default
156
+
157
+
Example:
158
+
>go
159
+
// before
160
+
type User struct {
161
+
// ^ put your cursor here
162
+
// run `:GoTagAdd yaml`
163
+
ID int
164
+
Name string
165
+
}
166
+
167
+
// after
168
+
type User struct {
169
+
ID int `yaml:id`
170
+
Name string `yaml:name`
171
+
}
172
+
<
173
+
174
+
==============================================================================
175
+
------------------------------------------------------------------------------
176
+
*gopher.nvim-json2go*
177
+
178
+
Convert json to go type annotations.
179
+
180
+
Usage ~
181
+
182
+
`:GoJson` opens a temporary buffer where you can paste or write JSON.
183
+
Saving the buffer (`:w` or `:wq`) automatically closes it and inserts the
184
+
generated Go struct into the original buffer at the cursor position.
185
+
186
+
Alternatively, you can pass JSON directly as an argument:
187
+
>vim
188
+
:GoJson {"name": "Alice", "age": 30}
189
+
<
190
+
------------------------------------------------------------------------------
191
+
*json2go.transform()*
192
+
`json2go.transform`({json_str})
193
+
194
+
Parameters ~
195
+
{json_str} `(string)` Json string that is going to be converted to go type.
196
+
Return ~
197
+
`(string)` `(optional)` Go type, or nil if failed.
198
+
199
+
------------------------------------------------------------------------------
200
+
*json2go.json2go()*
201
+
`json2go.json2go`({json_str})
202
+
Converts json string to go type, and puts result under the cursor. If
203
+
[json_str] is nil, will open an interactive prompt (with cmd set in
204
+
config).
205
+
206
+
Parameters ~
207
+
{json_str} `(optional)` `(string)`
208
+
209
+
210
+
==============================================================================
211
+
------------------------------------------------------------------------------
212
+
*gopher.nvim-impl*
213
+
214
+
Integration of `impl` tool to generate method stubs for interfaces.
215
+
216
+
Usage ~
217
+
218
+
1. Automatically implement an interface for a struct:
219
+
- Place your cursor on the struct where you want to implement the interface.
220
+
- Run `:GoImpl io.Reader`
221
+
- This will automatically determine the receiver and implement the `io.Reader` interface.
222
+
223
+
2. Specify a custom receiver:
224
+
- Place your cursor on the struct
225
+
- Run `:GoImpl w io.Writer`, where:
226
+
- `w` is the receiver.
227
+
- `io.Writer` is the interface to implement.
228
+
229
+
3. Explicitly specify the receiver, struct, and interface:
230
+
- No need to place the cursor on the struct if all arguments are provided.
231
+
- Run `:GoImpl r RequestReader io.Reader`, where:
232
+
- `r` is the receiver.
233
+
- `RequestReader` is the struct.
234
+
- `io.Reader` is the interface to implement.
235
+
236
+
Example:
237
+
>go
238
+
type BytesReader struct{}
239
+
// ^ put your cursor here
240
+
// run `:GoImpl b io.Reader`
241
+
242
+
// this is what you will get
243
+
func (b *BytesReader) Read(p []byte) (n int, err error) {
244
+
panic("not implemented") // TODO: Implement
245
+
}
246
+
<
247
+
248
+
==============================================================================
249
+
------------------------------------------------------------------------------
250
+
*gopher.nvim-gotests*
251
+
gotests is utilizing the `gotests` tool to generate unit tests boilerplate.
252
+
Usage ~
253
+
254
+
- Generate unit test for specific function/method:
255
+
1. Place your cursor on the desired function/method.
256
+
2. Run `:GoTestAdd`
257
+
258
+
- Generate unit tests for *all* functions/methods in current file:
259
+
- run `:GoTestsAll`
260
+
261
+
- Generate unit tests *only* for *exported(public)* functions/methods:
262
+
- run `:GoTestsExp`
263
+
264
+
You can also specify the template to use for generating the tests.
265
+
See |gopher.nvim-config|.
266
+
More details about templates: https://github.com/cweill/gotests
267
+
268
+
If you prefer named tests, you can enable them in |gopher.nvim-config|.
269
+
270
+
271
+
==============================================================================
272
+
------------------------------------------------------------------------------
273
+
*gopher.nvim-iferr*
274
+
275
+
`iferr` provides a way to way to automatically insert `if err != nil` check.
276
+
If you want to change `-message` option of `iferr` tool, see |gopher.nvim-config|
277
+
278
+
Usage ~
279
+
Execute `:GoIfErr` near any `err` variable to insert the check
280
+
281
+
282
+
==============================================================================
283
+
------------------------------------------------------------------------------
284
+
*gopher.nvim-comments*
285
+
286
+
This module provides a way to generate comments for Go code.
287
+
288
+
Usage ~
289
+
290
+
Set cursor on line with function/method/struct/etc and
291
+
run `:GoCmt` to generate a comment.
292
+
293
+
294
+
vim:tw=78:ts=8:noet:ft=help:norl:
+63
-47
lua/gopher/_utils/init.lua
+63
-47
lua/gopher/_utils/init.lua
···
1
-
return {
2
-
---@param t table
3
-
---@return boolean
4
-
empty = function(t)
5
-
if t == nil then
6
-
return true
7
-
end
1
+
local c = require "gopher.config"
2
+
local log = require "gopher._utils.log"
3
+
local utils = {}
8
4
9
-
return next(t) == nil
10
-
end,
5
+
---@param msg string
6
+
---@param lvl? integer by default `vim.log.levels.INFO`
7
+
function utils.notify(msg, lvl)
8
+
lvl = lvl or vim.log.levels.INFO
9
+
vim.notify(msg, lvl, {
10
+
---@diagnostic disable-next-line:undefined-field
11
+
title = c.___plugin_name,
12
+
})
13
+
log.debug(msg)
14
+
end
15
+
16
+
---@param path string
17
+
---@return string
18
+
function utils.readfile_joined(path)
19
+
return table.concat(vim.fn.readfile(path), "\n")
20
+
end
11
21
12
-
---@param s string
13
-
---@return string
14
-
rtrim = function(s)
15
-
local n = #s
16
-
while n > 0 and s:find("^%s", n) do
17
-
n = n - 1
22
+
---@param t string[]
23
+
---@return string[]
24
+
function utils.remove_empty_lines(t)
25
+
local res = {}
26
+
for _, line in ipairs(t) do
27
+
if line ~= "" then
28
+
table.insert(res, line)
18
29
end
30
+
end
31
+
return res
32
+
end
19
33
20
-
return s:sub(1, n)
21
-
end,
22
-
23
-
---@param lib string
24
-
---@return boolean
25
-
lualib_is_found = function(lib)
26
-
local is_found, _ = pcall(require, lib)
27
-
return is_found
28
-
end,
34
+
---@param s string
35
+
---@return string
36
+
function utils.trimend(s)
37
+
local r, _ = string.gsub(s, "%s+$", "")
38
+
return r
39
+
end
29
40
30
-
---@param bin string
31
-
---@return boolean
32
-
binary_is_found = function(bin)
33
-
if vim.fn.executable(bin) == 1 then
34
-
return true
35
-
end
41
+
-- Since indentation can be spaces or tabs, that's my hack around it
42
+
---@param line string
43
+
---@param indent integer
44
+
---@return string
45
+
function utils.indent(line, indent)
46
+
local char = string.sub(line, 1, 1)
47
+
if char ~= " " and char ~= "\t" then
48
+
char = " "
49
+
end
50
+
return string.rep(char, indent)
51
+
end
36
52
37
-
return false
38
-
end,
53
+
---@generic T
54
+
---@param tbl T[]
55
+
---@return T[]
56
+
function utils.list_unique(tbl)
57
+
if vim.fn.has "nvim-0.12" == 1 then
58
+
return vim.list.unique(tbl)
59
+
end
39
60
40
-
---@param msg string
41
-
---@param lvl string|integer
42
-
notify = function(msg, lvl)
43
-
local l
44
-
if lvl == "error" or lvl == 4 then
45
-
l = vim.log.levels.ERROR
46
-
elseif lvl == "info" or lvl == 2 then
47
-
l = vim.log.levels.INFO
48
-
elseif lvl == "debug" or lvl == 1 then
49
-
l = vim.log.levels.DEBUG
61
+
for i = #tbl, 1, -1 do
62
+
for j = 1, i - 1 do
63
+
if tbl[i] == tbl[j] then
64
+
table.remove(tbl, i)
65
+
break
66
+
end
50
67
end
68
+
end
69
+
return tbl
70
+
end
51
71
52
-
vim.defer_fn(function()
53
-
vim.notify(msg, l)
54
-
end, 0)
55
-
end,
56
-
}
72
+
return utils
+171
lua/gopher/_utils/log.lua
+171
lua/gopher/_utils/log.lua
···
1
+
-- thanks https://github.com/tjdevries/vlog.nvim
2
+
-- and https://github.com/williamboman/mason.nvim
3
+
-- for the code i have stolen(or have inspected by idk)
4
+
local c = require "gopher.config"
5
+
6
+
---@class gopher.Logger
7
+
---@field get_outfile fun():string
8
+
---@field trace fun(...)
9
+
---@field fmt_trace fun(...)
10
+
---@field debug fun(...)
11
+
---@field fmt_debug fun(...)
12
+
---@field info fun(...)
13
+
---@field fmt_info fun(...)
14
+
---@field warn fun(...)
15
+
---@field fmt_warn fun(...)
16
+
---@field error fun(...)
17
+
---@field fmt_error fun(...)
18
+
19
+
local config = {
20
+
-- Name of the plugin. Prepended to log messages
21
+
---@diagnostic disable-next-line:undefined-field
22
+
name = c.___plugin_name,
23
+
24
+
-- Should print the output to neovim while running
25
+
-- values: 'sync','async',false
26
+
use_console = vim.env.GOPHER_VERBOSE_LOGS == "1",
27
+
28
+
-- Should highlighting be used in console (using echohl)
29
+
highlights = true,
30
+
31
+
-- Should write to a file
32
+
use_file = true,
33
+
34
+
-- Level configuration
35
+
modes = {
36
+
{ name = "trace", hl = "Comment", level = vim.log.levels.TRACE },
37
+
{ name = "debug", hl = "Comment", level = vim.log.levels.DEBUG },
38
+
{ name = "info", hl = "None", level = vim.log.levels.INFO },
39
+
{ name = "warn", hl = "WarningMsg", level = vim.log.levels.WARN },
40
+
{ name = "error", hl = "ErrorMsg", level = vim.log.levels.ERROR },
41
+
},
42
+
43
+
-- Can limit the number of decimals displayed for floats
44
+
float_precision = 0.01,
45
+
}
46
+
47
+
---@type gopher.Logger
48
+
---@diagnostic disable-next-line: missing-fields
49
+
local log = {}
50
+
51
+
---@return string
52
+
function log.get_outfile()
53
+
return table.concat {
54
+
(vim.fn.has "nvim-0.8.0" == 1) and vim.fn.stdpath "log" or vim.fn.stdpath "cache",
55
+
("/%s.log"):format(config.name),
56
+
}
57
+
end
58
+
59
+
-- selene: allow(incorrect_standard_library_use)
60
+
local unpack = unpack or table.unpack
61
+
62
+
do
63
+
local round = function(x, increment)
64
+
increment = increment or 1
65
+
x = x / increment
66
+
return (x > 0 and math.floor(x + 0.5) or math.ceil(x - 0.5)) * increment
67
+
end
68
+
69
+
local tbl_has_tostring = function(tbl)
70
+
local mt = getmetatable(tbl)
71
+
return mt and mt.__tostring ~= nil
72
+
end
73
+
74
+
local make_string = function(...)
75
+
local t = {}
76
+
for i = 1, select("#", ...) do
77
+
local x = select(i, ...)
78
+
79
+
if type(x) == "number" and config.float_precision then
80
+
x = tostring(round(x, config.float_precision))
81
+
elseif type(x) == "table" and not tbl_has_tostring(x) then
82
+
x = vim.inspect(x)
83
+
else
84
+
x = tostring(x)
85
+
end
86
+
87
+
t[#t + 1] = x
88
+
end
89
+
return table.concat(t, " ")
90
+
end
91
+
92
+
local log_at_level = function(level_config, message_maker, ...)
93
+
-- Return early if we're below the current_log_level
94
+
--
95
+
-- the log level source get from config directly because otherwise it doesn't work
96
+
if level_config.level < c.log_level then
97
+
return
98
+
end
99
+
local nameupper = level_config.name:upper()
100
+
101
+
local msg = message_maker(...)
102
+
local info = debug.getinfo(2, "Sl")
103
+
local lineinfo = info.short_src .. ":" .. info.currentline
104
+
105
+
-- Output to console
106
+
if config.use_console then
107
+
local log_to_console = function()
108
+
local console_string =
109
+
string.format("[%-6s%s] %s: %s", nameupper, os.date "%H:%M:%S", lineinfo, msg)
110
+
111
+
if config.highlights and level_config.hl then
112
+
vim.cmd(string.format("echohl %s", level_config.hl))
113
+
end
114
+
115
+
local split_console = vim.split(console_string, "\n")
116
+
for _, v in ipairs(split_console) do
117
+
local formatted_msg = string.format("[%s] %s", config.name, vim.fn.escape(v, [["\]]))
118
+
119
+
---@diagnostic disable-next-line: param-type-mismatch
120
+
local ok = pcall(vim.cmd, string.format([[echom "%s"]], formatted_msg))
121
+
if not ok then
122
+
vim.api.nvim_out_write(msg .. "\n")
123
+
end
124
+
end
125
+
126
+
if config.highlights and level_config.hl then
127
+
vim.cmd "echohl NONE"
128
+
end
129
+
end
130
+
if config.use_console == "sync" and not vim.in_fast_event() then
131
+
log_to_console()
132
+
else
133
+
vim.schedule(log_to_console)
134
+
end
135
+
end
136
+
137
+
-- Output to log file
138
+
if config.use_file then
139
+
local fp = assert(io.open(log.get_outfile(), "a"))
140
+
local str = string.format("[%-6s%s] %s: %s\n", nameupper, os.date(), lineinfo, msg)
141
+
fp:write(str)
142
+
fp:close()
143
+
end
144
+
end
145
+
146
+
for _, x in ipairs(config.modes) do
147
+
-- log.info("these", "are", "separated")
148
+
log[x.name] = function(...) ---@diagnostic disable-line: assign-type-mismatch
149
+
return log_at_level(x, make_string, ...)
150
+
end
151
+
152
+
-- log.fmt_info("These are %s strings", "formatted")
153
+
log[("fmt_%s"):format(x.name)] = function(...) ---@diagnostic disable-line: assign-type-mismatch
154
+
return log_at_level(x, function(...)
155
+
local passed = { ... }
156
+
local fmt = table.remove(passed, 1)
157
+
local inspected = {}
158
+
for _, v in ipairs(passed) do
159
+
if type(v) == "table" and tbl_has_tostring(v) then
160
+
table.insert(inspected, v)
161
+
else
162
+
table.insert(inspected, vim.inspect(v))
163
+
end
164
+
end
165
+
return string.format(fmt, unpack(inspected))
166
+
end, ...)
167
+
end
168
+
end
169
+
end
170
+
171
+
return log
+39
lua/gopher/_utils/runner.lua
+39
lua/gopher/_utils/runner.lua
···
1
+
local c = require "gopher.config"
2
+
local runner = {}
3
+
4
+
---@class gopher.RunnerOpts
5
+
---@field cwd? string
6
+
---@field timeout? number
7
+
---@field stdin? boolean|string|string[]
8
+
---@field text? boolean
9
+
10
+
---@param cmd (string|number)[]
11
+
---@param on_exit fun(out:vim.SystemCompleted)
12
+
---@param opts? gopher.RunnerOpts
13
+
---@return vim.SystemObj
14
+
function runner.async(cmd, on_exit, opts)
15
+
opts = opts or {}
16
+
return vim.system(cmd, {
17
+
cwd = opts.cwd or nil,
18
+
timeout = opts.timeout or c.timeout,
19
+
stdin = opts.stdin or nil,
20
+
text = opts.text or true,
21
+
}, on_exit)
22
+
end
23
+
24
+
---@param cmd (string|number)[]
25
+
---@param opts? gopher.RunnerOpts
26
+
---@return vim.SystemCompleted
27
+
function runner.sync(cmd, opts)
28
+
opts = opts or {}
29
+
return vim
30
+
.system(cmd, {
31
+
cwd = opts.cwd or nil,
32
+
timeout = opts.timeout or c.timeout,
33
+
stdin = opts.stdin or nil,
34
+
text = opts.text or true,
35
+
})
36
+
:wait()
37
+
end
38
+
39
+
return runner
-87
lua/gopher/_utils/ts/init.lua
-87
lua/gopher/_utils/ts/init.lua
···
1
-
local nodes = require "gopher._utils.ts.nodes"
2
-
local u = require "gopher._utils"
3
-
local M = {
4
-
querys = {
5
-
struct_block = [[((type_declaration (type_spec name:(type_identifier) @struct.name type: (struct_type)))@struct.declaration)]],
6
-
em_struct_block = [[(field_declaration name:(field_identifier)@struct.name type: (struct_type)) @struct.declaration]],
7
-
package = [[(package_clause (package_identifier)@package.name)@package.clause]],
8
-
interface = [[((type_declaration (type_spec name:(type_identifier) @interface.name type:(interface_type)))@interface.declaration)]],
9
-
method_name = [[((method_declaration receiver: (parameter_list)@method.receiver name: (field_identifier)@method.name body:(block))@method.declaration)]],
10
-
func = [[((function_declaration name: (identifier)@function.name) @function.declaration)]],
11
-
},
12
-
}
13
-
14
-
---@return table
15
-
local function get_name_defaults()
16
-
return {
17
-
["func"] = "function",
18
-
["if"] = "if",
19
-
["else"] = "else",
20
-
["for"] = "for",
21
-
}
22
-
end
23
-
24
-
---@param row string
25
-
---@param col string
26
-
---@param bufnr string|nil
27
-
---@return table|nil
28
-
function M.get_struct_node_at_pos(row, col, bufnr)
29
-
local query = M.querys.struct_block .. " " .. M.querys.em_struct_block
30
-
local bufn = bufnr or vim.api.nvim_get_current_buf()
31
-
local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col)
32
-
if ns == nil then
33
-
u.notify("struct not found", "warn")
34
-
else
35
-
return ns[#ns]
36
-
end
37
-
end
38
-
39
-
---@param row string
40
-
---@param col string
41
-
---@param bufnr string|nil
42
-
---@return table|nil
43
-
function M.get_func_method_node_at_pos(row, col, bufnr)
44
-
local query = M.querys.func .. " " .. M.querys.method_name
45
-
local bufn = bufnr or vim.api.nvim_get_current_buf()
46
-
local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col)
47
-
if ns == nil then
48
-
u.notify("function not found", "warn")
49
-
else
50
-
return ns[#ns]
51
-
end
52
-
end
53
-
54
-
---@param row string
55
-
---@param col string
56
-
---@param bufnr string|nil
57
-
---@return table|nil
58
-
function M.get_package_node_at_pos(row, col, bufnr)
59
-
-- stylua: ignore
60
-
if row > 10 then return end
61
-
local query = M.querys.package
62
-
local bufn = bufnr or vim.api.nvim_get_current_buf()
63
-
local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col)
64
-
if ns == nil then
65
-
u.notify("package not found", "warn")
66
-
return nil
67
-
else
68
-
return ns[#ns]
69
-
end
70
-
end
71
-
72
-
---@param row string
73
-
---@param col string
74
-
---@param bufnr string|nil
75
-
---@return table|nil
76
-
function M.get_interface_node_at_pos(row, col, bufnr)
77
-
local query = M.querys.interface
78
-
local bufn = bufnr or vim.api.nvim_get_current_buf()
79
-
local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col)
80
-
if ns == nil then
81
-
u.notify("interface not found", "warn")
82
-
else
83
-
return ns[#ns]
84
-
end
85
-
end
86
-
87
-
return M
-137
lua/gopher/_utils/ts/nodes.lua
-137
lua/gopher/_utils/ts/nodes.lua
···
1
-
local ts_query = require "nvim-treesitter.query"
2
-
local parsers = require "nvim-treesitter.parsers"
3
-
local locals = require "nvim-treesitter.locals"
4
-
local u = require "gopher._utils"
5
-
local M = {}
6
-
7
-
local function intersects(row, col, sRow, sCol, eRow, eCol)
8
-
if sRow > row or eRow < row then
9
-
return false
10
-
end
11
-
12
-
if sRow == row and sCol > col then
13
-
return false
14
-
end
15
-
16
-
if eRow == row and eCol < col then
17
-
return false
18
-
end
19
-
20
-
return true
21
-
end
22
-
23
-
---@param nodes table
24
-
---@param row string
25
-
---@param col string
26
-
---@return table
27
-
function M.intersect_nodes(nodes, row, col)
28
-
local found = {}
29
-
for idx = 1, #nodes do
30
-
local node = nodes[idx]
31
-
local sRow = node.dim.s.r
32
-
local sCol = node.dim.s.c
33
-
local eRow = node.dim.e.r
34
-
local eCol = node.dim.e.c
35
-
36
-
if intersects(row, col, sRow, sCol, eRow, eCol) then
37
-
table.insert(found, node)
38
-
end
39
-
end
40
-
41
-
return found
42
-
end
43
-
44
-
---@param nodes table
45
-
---@return table
46
-
function M.sort_nodes(nodes)
47
-
table.sort(nodes, function(a, b)
48
-
return M.count_parents(a) < M.count_parents(b)
49
-
end)
50
-
51
-
return nodes
52
-
end
53
-
54
-
---@param query string
55
-
---@param lang string
56
-
---@param bufnr integer
57
-
---@param pos_row string
58
-
---@return string
59
-
function M.get_all_nodes(query, lang, _, bufnr, pos_row, _)
60
-
bufnr = bufnr or 0
61
-
pos_row = pos_row or 30000
62
-
63
-
local ok, parsed_query = pcall(function()
64
-
return vim.treesitter.parse_query(lang, query)
65
-
end)
66
-
if not ok then
67
-
return nil
68
-
end
69
-
70
-
local parser = parsers.get_parser(bufnr, lang)
71
-
local root = parser:parse()[1]:root()
72
-
local start_row, _, end_row, _ = root:range()
73
-
local results = {}
74
-
75
-
for match in ts_query.iter_prepared_matches(parsed_query, root, bufnr, start_row, end_row) do
76
-
local sRow, sCol, eRow, eCol, declaration_node
77
-
local type, name, op = "", "", ""
78
-
locals.recurse_local_nodes(match, function(_, node, path)
79
-
local idx = string.find(path, ".[^.]*$")
80
-
op = string.sub(path, idx + 1, #path)
81
-
type = string.sub(path, 1, idx - 1)
82
-
83
-
if op == "name" then
84
-
name = vim.treesitter.query.get_node_text(node, bufnr)
85
-
elseif op == "declaration" or op == "clause" then
86
-
declaration_node = node
87
-
sRow, sCol, eRow, eCol = node:range()
88
-
sRow = sRow + 1
89
-
eRow = eRow + 1
90
-
sCol = sCol + 1
91
-
eCol = eCol + 1
92
-
end
93
-
end)
94
-
95
-
if declaration_node ~= nil then
96
-
table.insert(results, {
97
-
declaring_node = declaration_node,
98
-
dim = { s = { r = sRow, c = sCol }, e = { r = eRow, c = eCol } },
99
-
name = name,
100
-
operator = op,
101
-
type = type,
102
-
})
103
-
end
104
-
end
105
-
106
-
return results
107
-
end
108
-
109
-
---@param query string
110
-
---@param default string
111
-
---@param bufnr string
112
-
---@param row string
113
-
---@param col string
114
-
---@return table
115
-
function M.nodes_at_cursor(query, default, bufnr, row, col)
116
-
bufnr = bufnr or vim.api.nvim_get_current_buf()
117
-
local ft = vim.api.nvim_buf_get_option(bufnr, "ft")
118
-
if row == nil or col == nil then
119
-
row, col = unpack(vim.api.nvim_win_get_cursor(0))
120
-
end
121
-
122
-
local nodes = M.get_all_nodes(query, ft, default, bufnr, row, col)
123
-
if nodes == nil then
124
-
u.notify("Unable to find any nodes. Place your cursor on a go symbol and try again", "debug")
125
-
return nil
126
-
end
127
-
128
-
nodes = M.sort_nodes(M.intersect_nodes(nodes, row, col))
129
-
if nodes == nil or #nodes == 0 then
130
-
u.notify("Unable to find any nodes at pos. " .. tostring(row) .. ":" .. tostring(col), "debug")
131
-
return nil
132
-
end
133
-
134
-
return nodes
135
-
end
136
-
137
-
return M
+160
lua/gopher/_utils/ts.lua
+160
lua/gopher/_utils/ts.lua
···
1
+
local ts = {}
2
+
local queries = {
3
+
struct = [[
4
+
[(type_spec name: (type_identifier) @_name
5
+
type: (struct_type))
6
+
(var_declaration (var_spec
7
+
name: (identifier) @_name @_var
8
+
type: (struct_type)))
9
+
(short_var_declaration
10
+
left: (expression_list (identifier) @_name @_var)
11
+
right: (expression_list (composite_literal
12
+
type: (struct_type))))]
13
+
]],
14
+
struct_field = [[
15
+
(field_declaration name: (field_identifier) @_name)
16
+
]],
17
+
func = [[
18
+
[(function_declaration name: (identifier) @_name)
19
+
(method_declaration name: (field_identifier) @_name)
20
+
(method_elem name: (field_identifier) @_name)]
21
+
]],
22
+
package = [[
23
+
(package_identifier) @_name
24
+
]],
25
+
interface = [[
26
+
(type_spec
27
+
name: (type_identifier) @_name
28
+
type: (interface_type))
29
+
]],
30
+
var = [[
31
+
[(var_declaration (var_spec name: (identifier) @_name))
32
+
(short_var_declaration
33
+
left: (expression_list (identifier) @_name @_var))]
34
+
]],
35
+
}
36
+
37
+
---@param parent_type string[]
38
+
---@param node TSNode
39
+
---@return TSNode?
40
+
local function get_parent_node(parent_type, node)
41
+
---@type TSNode?
42
+
local current = node
43
+
while current do
44
+
if vim.tbl_contains(parent_type, current:type()) then
45
+
break
46
+
end
47
+
48
+
current = current:parent()
49
+
if current == nil then
50
+
return nil
51
+
end
52
+
end
53
+
return current
54
+
end
55
+
56
+
---@param query vim.treesitter.Query
57
+
---@param node TSNode
58
+
---@param bufnr integer
59
+
---@return {name:string, is_varstruct:boolean}
60
+
local function get_captures(query, node, bufnr)
61
+
local res = {}
62
+
for id, _node in query:iter_captures(node, bufnr) do
63
+
if query.captures[id] == "_name" then
64
+
res["name"] = vim.treesitter.get_node_text(_node, bufnr)
65
+
end
66
+
67
+
if query.captures[id] == "_var" then
68
+
res["is_varstruct"] = true
69
+
end
70
+
end
71
+
72
+
return res
73
+
end
74
+
75
+
---@class gopher.TsResult
76
+
---@field name string Name of the struct, function, etc
77
+
---@field start integer Line number where the declaration starts
78
+
---@field end_ integer Line number where the declaration ends
79
+
---@field indent integer Number of spaces/tabs in the current cursor line
80
+
---@field is_varstruct boolean Is struct declared as `var S struct{}` or `s := struct{}{}`
81
+
82
+
---@param bufnr integer
83
+
---@param parent_type string[]
84
+
---@param query string
85
+
---@return gopher.TsResult
86
+
local function do_stuff(bufnr, parent_type, query)
87
+
if not vim.treesitter.get_parser(bufnr, "go") then
88
+
error "No treesitter parser found for go"
89
+
end
90
+
91
+
local node = vim.treesitter.get_node { bufnr = bufnr }
92
+
if not node then
93
+
error "No nodes found under the cursor"
94
+
end
95
+
96
+
local parent_node = get_parent_node(parent_type, node)
97
+
if not parent_node then
98
+
error "No parent node found under the cursor"
99
+
end
100
+
101
+
local q = vim.treesitter.query.parse("go", query)
102
+
local res = get_captures(q, parent_node, bufnr)
103
+
assert(res.name ~= nil, "No capture name found")
104
+
105
+
local start_row, start_col, end_row, _ = parent_node:range()
106
+
res["indent"] = start_col
107
+
res["start"] = start_row + 1
108
+
res["end_"] = end_row + 1
109
+
110
+
return res
111
+
end
112
+
113
+
---@param bufnr integer
114
+
function ts.get_struct_under_cursor(bufnr)
115
+
--- should be both type_spec and type_declaration
116
+
--- because in cases like `type ( T struct{}, U struct{} )`
117
+
---
118
+
--- var_declaration is for cases like `var x struct{}`
119
+
--- short_var_declaration is for cases like `x := struct{}{}`
120
+
---
121
+
--- it always chooses last struct type in the list
122
+
return do_stuff(bufnr, {
123
+
"type_spec",
124
+
"type_declaration",
125
+
"var_declaration",
126
+
"short_var_declaration",
127
+
}, queries.struct)
128
+
end
129
+
130
+
---@param bufnr integer
131
+
function ts.get_struct_field_under_cursor(bufnr)
132
+
return do_stuff(bufnr, { "field_declaration" }, queries.struct_field)
133
+
end
134
+
135
+
---@param bufnr integer
136
+
function ts.get_func_under_cursor(bufnr)
137
+
--- since this handles both and funcs and methods we should check for both parent nodes
138
+
return do_stuff(bufnr, {
139
+
"method_elem",
140
+
"function_declaration",
141
+
"method_declaration",
142
+
}, queries.func)
143
+
end
144
+
145
+
---@param bufnr integer
146
+
function ts.get_package_under_cursor(bufnr)
147
+
return do_stuff(bufnr, { "package_clause" }, queries.package)
148
+
end
149
+
150
+
---@param bufnr integer
151
+
function ts.get_interface_under_cursor(bufnr)
152
+
return do_stuff(bufnr, { "type_declaration" }, queries.interface)
153
+
end
154
+
155
+
---@param bufnr integer
156
+
function ts.get_variable_under_cursor(bufnr)
157
+
return do_stuff(bufnr, { "var_declaration", "short_var_declaration" }, queries.var)
158
+
end
159
+
160
+
return ts
+55
-34
lua/gopher/comment.lua
+55
-34
lua/gopher/comment.lua
···
1
-
local ts_utils = require "gopher._utils.ts"
1
+
---@toc_entry Generate comments
2
+
---@tag gopher.nvim-comments
3
+
---@text
4
+
--- This module provides a way to generate comments for Go code.
5
+
---
6
+
---@usage
7
+
--- Set cursor on line with function/method/struct/etc and
8
+
--- run `:GoCmt` to generate a comment.
2
9
3
-
local function generate(row, col)
4
-
local comment, ns = nil, nil
10
+
local ts = require "gopher._utils.ts"
11
+
local log = require "gopher._utils.log"
12
+
local u = require "gopher._utils"
13
+
local comment = {}
5
14
6
-
ns = ts_utils.get_package_node_at_pos(row, col)
7
-
if ns ~= nil and ns ~= {} then
8
-
comment = "// Package " .. ns.name .. " provides " .. ns.name
9
-
return comment, ns
15
+
--- NOTE: The order of functions executed inside this function is IMPORTANT.
16
+
--- This function is extremely fragile; run tests after making any changes.
17
+
---
18
+
---@param bufnr integer
19
+
---@param line string
20
+
---@return string
21
+
---@dochide
22
+
local function generate(bufnr, line)
23
+
local sf_ok, sf_res = pcall(ts.get_struct_field_under_cursor, bufnr)
24
+
if sf_ok then
25
+
return u.indent(line, sf_res.indent) .. "// " .. sf_res.name .. " "
10
26
end
11
27
12
-
ns = ts_utils.get_struct_node_at_pos(row, col)
13
-
if ns ~= nil and ns ~= {} then
14
-
comment = "// " .. ns.name .. " " .. ns.type .. " "
15
-
return comment, ns
28
+
local s_ok, s_res = pcall(ts.get_struct_under_cursor, bufnr)
29
+
if s_ok then
30
+
return u.indent(line, s_res.indent) .. "// " .. s_res.name .. " "
16
31
end
17
32
18
-
ns = ts_utils.get_func_method_node_at_pos(row, col)
19
-
if ns ~= nil and ns ~= {} then
20
-
comment = "// " .. ns.name .. " " .. ns.type .. " "
21
-
return comment, ns
33
+
local v_ok, v_res = pcall(ts.get_variable_under_cursor, bufnr)
34
+
if v_ok then
35
+
return u.indent(line, v_res.indent) .. "// " .. v_res.name .. " "
22
36
end
23
37
24
-
ns = ts_utils.get_interface_node_at_pos(row, col)
25
-
if ns ~= nil and ns ~= {} then
26
-
comment = "// " .. ns.name .. " " .. ns.type .. " "
27
-
return comment, ns
38
+
local f_ok, f_res = pcall(ts.get_func_under_cursor, bufnr)
39
+
if f_ok then
40
+
return u.indent(line, f_res.indent) .. "// " .. f_res.name .. " "
28
41
end
29
42
30
-
return "// ", {}
31
-
end
32
-
33
-
return function()
34
-
local row, col = unpack(vim.api.nvim_win_get_cursor(0))
35
-
local comment, ns = generate(row + 1, col + 1)
43
+
local i_ok, i_res = pcall(ts.get_interface_under_cursor, bufnr)
44
+
if i_ok then
45
+
return u.indent(line, i_res.indent) .. "// " .. i_res.name .. " "
46
+
end
36
47
37
-
vim.api.nvim_win_set_cursor(0, {
38
-
ns.dim.s.r,
39
-
ns.dim.s.c,
40
-
})
48
+
local p_ok, p_res = pcall(ts.get_package_under_cursor, bufnr)
49
+
if p_ok then
50
+
return "// Package " .. p_res.name .. " provides "
51
+
end
41
52
42
-
vim.fn.append(row - 1, comment)
53
+
return "// "
54
+
end
43
55
44
-
vim.api.nvim_win_set_cursor(0, {
45
-
ns.dim.s.r,
46
-
#comment + 1,
56
+
function comment.comment()
57
+
local bufnr = vim.api.nvim_get_current_buf()
58
+
local lnum = vim.fn.getcurpos()[2]
59
+
local line = vim.fn.getline(lnum)
60
+
local cmt = generate(bufnr, line)
61
+
log.debug("generated comment:", {
62
+
comment = cmt,
63
+
line = line,
47
64
})
48
65
49
-
vim.cmd [[startinsert!]]
66
+
vim.fn.append(lnum - 1, cmt)
67
+
vim.fn.setpos(".", { bufnr, lnum, #cmt })
68
+
vim.cmd "startinsert!"
50
69
end
70
+
71
+
return comment
+137
-14
lua/gopher/config.lua
+137
-14
lua/gopher/config.lua
···
1
-
local M = {
2
-
config = {
3
-
---set custom commands for tools
4
-
commands = {
5
-
go = "go",
6
-
gomodifytags = "gomodifytags",
7
-
gotests = "gotests",
8
-
impl = "impl",
9
-
},
1
+
---@type gopher.Config
2
+
---@dochide
3
+
---@diagnostic disable-next-line: missing-fields .setup() gets injected later
4
+
local config = {}
5
+
6
+
---@tag gopher.nvim-config.ConfigGoTagTransform
7
+
---@text Possible values for |gopher.Config|.gotag.transform:
8
+
---
9
+
---@dochide
10
+
---@alias gopher.ConfigGoTagTransform
11
+
---| "snakecase" "GopherUser" -> "gopher_user"
12
+
---| "camelcase" "GopherUser" -> "gopherUser"
13
+
---| "lispcase" "GopherUser" -> "gopher-user"
14
+
---| "pascalcase" "GopherUser" -> "GopherUser"
15
+
---| "titlecase" "GopherUser" -> "Gopher User"
16
+
---| "keep" keeps the original field name
17
+
18
+
---@toc_entry Config
19
+
---@tag gopher.nvim-config
20
+
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
21
+
---@class gopher.Config
22
+
---@field setup fun(user_config?: gopher.Config)
23
+
local default_config = {
24
+
-- log level, you might consider using DEBUG or TRACE for debugging the plugin
25
+
---@type number
26
+
log_level = vim.log.levels.INFO,
27
+
28
+
-- timeout for running internal commands
29
+
---@type number
30
+
timeout = 2000,
31
+
32
+
-- timeout for running installer commands(e.g :GoDepsInstall, :GoDepsInstallSync)
33
+
---@type number
34
+
installer_timeout = 999999,
35
+
36
+
-- user specified paths to binaries
37
+
---@class gopher.ConfigCommand
38
+
commands = {
39
+
go = "go",
40
+
gomodifytags = "gomodifytags",
41
+
gotests = "gotests",
42
+
impl = "impl",
43
+
iferr = "iferr",
44
+
json2go = "json2go",
45
+
},
46
+
---@class gopher.ConfigGotests
47
+
gotests = {
48
+
-- a default template that gotess will use.
49
+
-- gotets doesn't have template named `default`, we use it to represent absence of the provided template.
50
+
template = "default",
51
+
52
+
-- path to a directory containing custom test code templates
53
+
---@type string|nil
54
+
template_dir = nil,
55
+
56
+
-- use named tests(map with test name as key) in table tests(slice of structs by default)
57
+
named = false,
58
+
},
59
+
---@class gopher.ConfigGoTag
60
+
gotag = {
61
+
---@type gopher.ConfigGoTagTransform
62
+
transform = "snakecase",
63
+
64
+
-- default tags to add to struct fields
65
+
default_tag = "json",
66
+
67
+
-- default tag option added struct fields, set to nil to disable
68
+
-- e.g: `option = "json=omitempty,xml=omitempty`
69
+
---@type string|nil
70
+
option = nil,
71
+
},
72
+
---@class gopher.ConfigIfErr
73
+
iferr = {
74
+
-- choose a custom error message, nil to use default
75
+
-- e.g: `message = 'fmt.Errorf("failed to %w", err)'`
76
+
---@type string|nil
77
+
message = nil,
78
+
},
79
+
---@class gopher.ConfigJson2Go
80
+
json2go = {
81
+
-- command used to open interactive input.
82
+
-- e.g: `split`, `botright split`, `tabnew`
83
+
interactive_cmd = "vsplit",
84
+
85
+
-- name of autogenerated struct, if nil none, will the default one of json2go.
86
+
-- e.g: "MySuperCoolName"
87
+
---@type string|nil
88
+
type_name = nil,
10
89
},
11
90
}
91
+
--minidoc_afterlines_end
92
+
93
+
---@type gopher.Config
94
+
---@dochide
95
+
local _config = default_config
96
+
97
+
-- I am kinda secret so don't tell anyone about me even dont use me
98
+
--
99
+
-- if you don't believe me that i am secret see
100
+
-- the line below it says @private
101
+
---@private
102
+
_config.___plugin_name = "gopher.nvim" ---@diagnostic disable-line: inject-field
103
+
104
+
---@param user_config? gopher.Config
105
+
---@dochide
106
+
function config.setup(user_config)
107
+
vim.validate("user_config", user_config, "table", true)
12
108
13
-
---Plugin setup function
14
-
---@param opts table user options
15
-
function M.setup(opts)
16
-
M.config = vim.tbl_deep_extend("force", M.config, opts)
109
+
_config = vim.tbl_deep_extend("force", vim.deepcopy(default_config), user_config or {})
110
+
111
+
vim.validate("log_level", _config.log_level, "number")
112
+
vim.validate("timeout", _config.timeout, "number")
113
+
vim.validate("installer_timeout", _config.timeout, "number")
114
+
vim.validate("commands", _config.commands, "table")
115
+
vim.validate("commands.go", _config.commands.go, "string")
116
+
vim.validate("commands.gomodifytags", _config.commands.gomodifytags, "string")
117
+
vim.validate("commands.gotests", _config.commands.gotests, "string")
118
+
vim.validate("commands.impl", _config.commands.impl, "string")
119
+
vim.validate("commands.iferr", _config.commands.iferr, "string")
120
+
vim.validate("commands.json2go", _config.commands.json2go, "string")
121
+
vim.validate("gotests", _config.gotests, "table")
122
+
vim.validate("gotests.template", _config.gotests.template, "string")
123
+
vim.validate("gotests.template_dir", _config.gotests.template_dir, { "string", "nil" })
124
+
vim.validate("gotests.named", _config.gotests.named, "boolean")
125
+
vim.validate("gotag", _config.gotag, "table")
126
+
vim.validate("gotag.transform", _config.gotag.transform, "string")
127
+
vim.validate("gotag.default_tag", _config.gotag.default_tag, "string")
128
+
vim.validate("gotag.option", _config.gotag.option, { "string", "nil" })
129
+
vim.validate("iferr", _config.iferr, "table")
130
+
vim.validate("iferr.message", _config.iferr.message, { "string", "nil" })
131
+
vim.validate("json2go.installer_timeout", _config.json2go.interactive_cmd, "string")
132
+
vim.validate("json2go.type_name", _config.json2go.type_name, { "string", "nil" })
17
133
end
18
134
19
-
return M
135
+
setmetatable(config, {
136
+
__index = function(_, key)
137
+
return _config[key]
138
+
end,
139
+
})
140
+
141
+
---@dochide
142
+
return config
+56
lua/gopher/go.lua
+56
lua/gopher/go.lua
···
1
+
local c = require "gopher.config"
2
+
local u = require "gopher._utils"
3
+
local r = require "gopher._utils.runner"
4
+
local go = {}
5
+
6
+
local function run(subcmd, args)
7
+
local rs = r.sync { c.commands.go, subcmd, unpack(args) }
8
+
if rs.code ~= 0 then
9
+
error("go " .. subcmd .. " failed: " .. rs.stderr)
10
+
end
11
+
12
+
u.notify(c.commands.go .. " " .. subcmd .. " ran successful")
13
+
return rs.stdout
14
+
end
15
+
16
+
---@param args string[]
17
+
function go.get(args)
18
+
for i, arg in ipairs(args) do
19
+
local m = string.match(arg, "^https://(.*)$") or string.match(arg, "^http://(.*)$") or arg
20
+
table.remove(args, i)
21
+
table.insert(args, i, m)
22
+
end
23
+
24
+
run("get", args)
25
+
end
26
+
27
+
---@param args string[]
28
+
function go.mod(args)
29
+
run("mod", args)
30
+
end
31
+
32
+
---@param args string[]
33
+
function go.work(args)
34
+
-- TODO: use `gopls.tidy`
35
+
36
+
run("work", args)
37
+
end
38
+
39
+
---Executes `go generate`
40
+
---If only argument is `%` it's going to be equivalent to `go generate <path to current file>`
41
+
---@param args string[]
42
+
function go.generate(args)
43
+
-- TODO: use `gopls.generate`
44
+
45
+
if #args == 0 then
46
+
error "please provide arguments"
47
+
end
48
+
49
+
if #args == 1 and args[1] == "%" then
50
+
args[1] = vim.fn.expand "%"
51
+
end
52
+
53
+
run("generate", args)
54
+
end
55
+
56
+
return go
-28
lua/gopher/gogenerate.lua
-28
lua/gopher/gogenerate.lua
···
1
-
local Job = require "plenary.job"
2
-
local c = require("gopher.config").config.commands
3
-
local u = require "gopher._utils"
4
-
5
-
---run "go generate"
6
-
return function(...)
7
-
local args = { ... }
8
-
if #args == 1 and args[1] == "%" then
9
-
args[1] = vim.fn.expand "%" ---@diagnostic disable-line: missing-parameter
10
-
end
11
-
12
-
local cmd_args = vim.list_extend({ "generate" }, args)
13
-
14
-
Job
15
-
:new({
16
-
command = c.go,
17
-
args = cmd_args,
18
-
on_exit = function(_, retval)
19
-
if retval ~= 0 then
20
-
u.notify("command 'go " .. unpack(cmd_args) .. "' exited with code " .. retval, "error")
21
-
return
22
-
end
23
-
24
-
u.notify("go generate was success runned", "info")
25
-
end,
26
-
})
27
-
:start()
28
-
end
-35
lua/gopher/goget.lua
-35
lua/gopher/goget.lua
···
1
-
local Job = require "plenary.job"
2
-
local c = require("gopher.config").config.commands
3
-
local u = require "gopher._utils"
4
-
5
-
---run "go get"
6
-
return function(...)
7
-
local args = { ... }
8
-
if #args == 0 then
9
-
u.notify("please provide a package url to get", "error")
10
-
return
11
-
end
12
-
13
-
for i, arg in ipairs(args) do
14
-
local m = string.match(arg, "^https://(.*)$") or string.match(arg, "^http://(.*)$") or arg
15
-
table.remove(args, i)
16
-
table.insert(args, i, m)
17
-
end
18
-
19
-
local cmd_args = vim.list_extend({ "get" }, args)
20
-
21
-
Job
22
-
:new({
23
-
command = c.go,
24
-
args = cmd_args,
25
-
on_exit = function(_, retval)
26
-
if retval ~= 0 then
27
-
u.notify("command 'go " .. unpack(cmd_args) .. "' exited with code " .. retval, "error")
28
-
return
29
-
end
30
-
31
-
u.notify("go get was success runned", "info")
32
-
end,
33
-
})
34
-
:start()
35
-
end
-29
lua/gopher/gomod.lua
-29
lua/gopher/gomod.lua
···
1
-
local Job = require "plenary.job"
2
-
local c = require("gopher.config").config.commands
3
-
local u = require "gopher._utils"
4
-
5
-
---run "go mod"
6
-
return function(...)
7
-
local args = { ... }
8
-
if #args == 0 then
9
-
u.notify("please provide any mod command", "error")
10
-
return
11
-
end
12
-
13
-
local cmd_args = vim.list_extend({ "mod" }, args)
14
-
15
-
Job
16
-
:new({
17
-
command = c.go,
18
-
args = cmd_args,
19
-
on_exit = function(_, retval)
20
-
if retval ~= 0 then
21
-
u.notify("command 'go " .. unpack(cmd_args) .. "' exited with code " .. retval, "error")
22
-
return
23
-
end
24
-
25
-
u.notify("go mod was success runned", "info")
26
-
end,
27
-
})
28
-
:start()
29
-
end
+58
-55
lua/gopher/gotests.lua
+58
-55
lua/gopher/gotests.lua
···
1
-
local Job = require "plenary.job"
1
+
---@toc_entry Generating unit tests boilerplate
2
+
---@tag gopher.nvim-gotests
3
+
---@text gotests is utilizing the `gotests` tool to generate unit tests boilerplate.
4
+
---@usage
5
+
--- - Generate unit test for specific function/method:
6
+
--- 1. Place your cursor on the desired function/method.
7
+
--- 2. Run `:GoTestAdd`
8
+
---
9
+
--- - Generate unit tests for *all* functions/methods in current file:
10
+
--- - run `:GoTestsAll`
11
+
---
12
+
--- - Generate unit tests *only* for *exported(public)* functions/methods:
13
+
--- - run `:GoTestsExp`
14
+
---
15
+
--- You can also specify the template to use for generating the tests.
16
+
--- See |gopher.nvim-config|.
17
+
--- More details about templates: https://github.com/cweill/gotests
18
+
---
19
+
--- If you prefer named tests, you can enable them in |gopher.nvim-config|.
20
+
21
+
local c = require "gopher.config"
2
22
local ts_utils = require "gopher._utils.ts"
3
-
local c = require("gopher.config").config.commands
23
+
local r = require "gopher._utils.runner"
4
24
local u = require "gopher._utils"
5
-
local M = {}
25
+
local log = require "gopher._utils.log"
26
+
local gotests = {}
27
+
28
+
---@param args table
29
+
---@dochide
30
+
local function add_test(args)
31
+
if c.gotests.named then
32
+
table.insert(args, "-named")
33
+
end
6
34
7
-
---@param cmd_args table
8
-
local function run(cmd_args)
9
-
Job
10
-
:new({
11
-
command = c.gotests,
12
-
args = cmd_args,
13
-
on_exit = function(_, retval)
14
-
if retval ~= 0 then
15
-
u.notify("command 'go " .. unpack(cmd_args) .. "' exited with code " .. retval, "error")
16
-
return
17
-
end
35
+
if c.gotests.template_dir then
36
+
table.insert(args, "-template_dir")
37
+
table.insert(args, c.gotests.template_dir)
38
+
end
18
39
19
-
u.notify("unit test(s) generated", "info")
20
-
end,
21
-
})
22
-
:start()
23
-
end
40
+
if c.gotests.template ~= "default" then
41
+
table.insert(args, "-template")
42
+
table.insert(args, c.gotests.template)
43
+
end
24
44
25
-
---@param args table
26
-
local function add_test(args)
27
-
local fpath = vim.fn.expand "%" ---@diagnostic disable-line: missing-parameter
28
45
table.insert(args, "-w")
29
-
table.insert(args, fpath)
30
-
run(args)
31
-
end
46
+
table.insert(args, vim.fn.expand "%")
32
47
33
-
---generate unit test for one function
34
-
---@param parallel boolean
35
-
function M.func_test(parallel)
36
-
local ns = ts_utils.get_func_method_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0)))
37
-
if ns == nil or ns.name == nil then
38
-
u.notify("cursor on func/method and execute the command again", "info")
39
-
return
40
-
end
48
+
log.debug("generating tests with args: ", args)
41
49
42
-
local cmd_args = { "-only", ns.name }
43
-
if parallel then
44
-
table.insert(cmd_args, "-parallel")
50
+
local rs = r.sync { c.commands.gotests, unpack(args) }
51
+
if rs.code ~= 0 then
52
+
error("gotests failed: " .. rs.stderr)
45
53
end
46
54
47
-
add_test(cmd_args)
55
+
u.notify "unit test(s) generated"
48
56
end
49
57
50
-
---generate unit tests for all functions in current file
51
-
---@param parallel boolean
52
-
function M.all_tests(parallel)
53
-
local cmd_args = { "-all" }
54
-
if parallel then
55
-
table.insert(cmd_args, "-parallel")
56
-
end
58
+
-- generate unit test for one function
59
+
function gotests.func_test()
60
+
local bufnr = vim.api.nvim_get_current_buf()
61
+
local func = ts_utils.get_func_under_cursor(bufnr)
57
62
58
-
add_test(cmd_args)
63
+
add_test { "-only", func.name }
59
64
end
60
65
61
-
---generate unit tests for all exported functions
62
-
---@param parallel boolean
63
-
function M.all_exported_tests(parallel)
64
-
local cmd_args = {}
65
-
if parallel then
66
-
table.insert(cmd_args, "-parallel")
67
-
end
66
+
-- generate unit tests for all functions in current file
67
+
function gotests.all_tests()
68
+
add_test { "-all" }
69
+
end
68
70
69
-
table.insert(cmd_args, "-exported")
70
-
add_test(cmd_args)
71
+
-- generate unit tests for all exported functions
72
+
function gotests.all_exported_tests()
73
+
add_test { "-exported" }
71
74
end
72
75
73
-
return M
76
+
return gotests
+49
-30
lua/gopher/health.lua
+49
-30
lua/gopher/health.lua
···
1
-
local health = vim.health or require "health"
2
-
local utils = require "gopher._utils"
3
-
local c = require("gopher.config").config.commands
1
+
local c = require("gopher.config").commands
2
+
local health = {}
4
3
5
-
local M = {
6
-
_required = {
7
-
plugins = {
8
-
{ lib = "plenary" },
9
-
{ lib = "nvim-treesitter" },
10
-
},
11
-
binarys = {
12
-
{ bin = c.go, help = "required for GoMod, GoGet, GoGenerate command" },
13
-
{ bin = c.gomodifytags, help = "required for modify struct tags" },
14
-
{ bin = c.impl, help = "required for interface implementing" },
15
-
{ bin = c.gotests, help = "required for test(s) generation" },
4
+
local deps = {
5
+
vim_version = "nvim-0.10",
6
+
bin = {
7
+
{
8
+
bin = c.go,
9
+
msg = "required for `:GoGet`, `:GoMod`, `:GoGenerate`, `:GoWork`, `:GoInstallDeps`, `:GoInstallDepsSync`",
16
10
},
11
+
{ bin = c.gomodifytags, msg = "required for `:GoTagAdd`, `:GoTagRm`" },
12
+
{ bin = c.impl, msg = "required for `:GoImpl`" },
13
+
{ bin = c.iferr, msg = "required for `:GoIfErr`" },
14
+
{ bin = c.gotests, msg = "required for `:GoTestAdd`, `:GoTestsAll`, `:GoTestsExp`" },
15
+
},
16
+
treesitter = {
17
+
{ parser = "go", msg = "required for most of the parts of `gopher.nvim`" },
17
18
},
18
19
}
19
20
20
-
function M.check()
21
-
health.report_start "Required plugins"
22
-
for _, plugin in ipairs(M._required.plugins) do
23
-
if utils.lualib_is_found(plugin.lib) then
24
-
health.report_ok(plugin.lib .. " installed.")
25
-
else
26
-
health.report_error(plugin.lib .. " not found. Gopher.nvim will not work without it!")
27
-
end
21
+
---@param bin {bin:string, msg:string, optional:boolean}
22
+
local function check_binary(bin)
23
+
if vim.fn.executable(bin.bin) == 1 then
24
+
vim.health.ok(bin.bin .. " is found oh PATH: `" .. vim.fn.exepath(bin.bin) .. "`")
25
+
else
26
+
vim.health.error(bin.bin .. " not found on PATH, " .. bin.msg)
27
+
end
28
+
end
29
+
30
+
---@param ts {parser:string, msg:string}
31
+
local function check_treesitter(ts)
32
+
local ok, parser = pcall(vim.treesitter.get_parser, 0, ts.parser)
33
+
if ok and parser ~= nil then
34
+
vim.health.ok("`" .. ts.parser .. "` parser is installed")
35
+
else
36
+
vim.health.error("`" .. ts.parser .. "` parser not found")
37
+
end
38
+
end
39
+
40
+
function health.check()
41
+
vim.health.start "Neovim version"
42
+
if vim.fn.has(deps.vim_version) == 1 then
43
+
vim.health.ok "Neovim version is compatible"
44
+
else
45
+
vim.health.error(deps.vim_version .. " or newer is required")
46
+
end
47
+
48
+
vim.health.start "Required binaries (those can be installed with `:GoInstallDeps`)"
49
+
for _, bin in ipairs(deps.bin) do
50
+
check_binary(bin)
28
51
end
29
52
30
-
health.report_start "Required go tools"
31
-
for _, binary in ipairs(M._required.binarys) do
32
-
if utils.binary_is_found(binary.bin) then
33
-
health.report_ok(binary.bin .. " installed")
34
-
else
35
-
health.report_warn(binary.bin .. " is not installed but " .. binary.help)
36
-
end
53
+
vim.health.start "Treesitter"
54
+
for _, parser in ipairs(deps.treesitter) do
55
+
check_treesitter(parser)
37
56
end
38
57
end
39
58
40
-
return M
59
+
return health
+48
lua/gopher/iferr.lua
+48
lua/gopher/iferr.lua
···
1
+
-- Thanks https://github.com/koron/iferr for vim implementation
2
+
3
+
---@toc_entry Iferr
4
+
---@tag gopher.nvim-iferr
5
+
---@text
6
+
--- `iferr` provides a way to way to automatically insert `if err != nil` check.
7
+
--- If you want to change `-message` option of `iferr` tool, see |gopher.nvim-config|
8
+
---
9
+
---@usage Execute `:GoIfErr` near any `err` variable to insert the check
10
+
11
+
local c = require "gopher.config"
12
+
local u = require "gopher._utils"
13
+
local r = require "gopher._utils.runner"
14
+
local log = require "gopher._utils.log"
15
+
local iferr = {}
16
+
17
+
function iferr.iferr()
18
+
local curb = vim.fn.wordcount().cursor_bytes
19
+
local pos = vim.fn.getcurpos()[2]
20
+
local fpath = vim.fn.expand "%"
21
+
22
+
local cmd = { c.commands.iferr, "-pos", curb }
23
+
if c.iferr.message ~= nil and type(c.iferr.message) == "string" then
24
+
table.insert(cmd, "-message")
25
+
table.insert(cmd, c.iferr.message)
26
+
end
27
+
28
+
local rs = r.sync(cmd, {
29
+
stdin = u.readfile_joined(fpath),
30
+
})
31
+
32
+
if rs.code ~= 0 then
33
+
if string.find(rs.stderr, "no functions at") then
34
+
u.notify("iferr: no function at " .. curb, vim.log.levels.ERROR)
35
+
log.warn("iferr: no function at " .. curb)
36
+
return
37
+
end
38
+
39
+
log.error("ferr: failed. output: " .. rs.stderr)
40
+
error("iferr failed: " .. rs.stderr)
41
+
end
42
+
43
+
vim.fn.append(pos, u.remove_empty_lines(vim.split(rs.stdout, "\n")))
44
+
vim.cmd [[silent normal! j=2j]]
45
+
vim.fn.setpos(".", pos --[[@as integer[] ]])
46
+
end
47
+
48
+
return iferr
+66
-61
lua/gopher/impl.lua
+66
-61
lua/gopher/impl.lua
···
1
-
local Job = require "plenary.job"
1
+
---@toc_entry Auto implementation of interface methods
2
+
---@tag gopher.nvim-impl
3
+
---@text
4
+
--- Integration of `impl` tool to generate method stubs for interfaces.
5
+
---
6
+
---@usage
7
+
--- 1. Automatically implement an interface for a struct:
8
+
--- - Place your cursor on the struct where you want to implement the interface.
9
+
--- - Run `:GoImpl io.Reader`
10
+
--- - This will automatically determine the receiver and implement the `io.Reader` interface.
11
+
---
12
+
--- 2. Specify a custom receiver:
13
+
--- - Place your cursor on the struct
14
+
--- - Run `:GoImpl w io.Writer`, where:
15
+
--- - `w` is the receiver.
16
+
--- - `io.Writer` is the interface to implement.
17
+
---
18
+
--- 3. Explicitly specify the receiver, struct, and interface:
19
+
--- - No need to place the cursor on the struct if all arguments are provided.
20
+
--- - Run `:GoImpl r RequestReader io.Reader`, where:
21
+
--- - `r` is the receiver.
22
+
--- - `RequestReader` is the struct.
23
+
--- - `io.Reader` is the interface to implement.
24
+
---
25
+
--- Example:
26
+
--- >go
27
+
--- type BytesReader struct{}
28
+
--- // ^ put your cursor here
29
+
--- // run `:GoImpl b io.Reader`
30
+
---
31
+
--- // this is what you will get
32
+
--- func (b *BytesReader) Read(p []byte) (n int, err error) {
33
+
--- panic("not implemented") // TODO: Implement
34
+
--- }
35
+
--- <
36
+
37
+
local c = require("gopher.config").commands
38
+
local r = require "gopher._utils.runner"
2
39
local ts_utils = require "gopher._utils.ts"
3
-
local c = require("gopher.config").config.commands
4
40
local u = require "gopher._utils"
5
-
6
-
---@return string
7
-
local function get_struct()
8
-
local ns = ts_utils.get_struct_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0)))
9
-
if ns == nil then
10
-
u.notify("put cursor on a struct or specify a receiver", "info")
11
-
return ""
12
-
end
13
-
14
-
vim.api.nvim_win_set_cursor(0, {
15
-
ns.dim.e.r,
16
-
ns.dim.e.c,
17
-
})
18
-
19
-
return ns.name
20
-
end
41
+
local impl = {}
21
42
22
-
return function(...)
43
+
function impl.impl(...)
23
44
local args = { ... }
24
-
local iface, recv_name = "", ""
25
-
local recv = get_struct()
45
+
local iface, recv = "", ""
46
+
local bufnr = vim.api.nvim_get_current_buf()
26
47
27
48
if #args == 0 then
28
-
iface = vim.fn.input "impl: generating method stubs for interface: "
29
-
vim.cmd "redeaw!"
30
-
if iface == "" then
31
-
u.notify("usage: GoImpl f *File io.Reader", "info")
32
-
return
33
-
end
49
+
u.notify("arguments not provided. usage: :GoImpl f *File io.Reader", vim.log.levels.ERROR)
50
+
return
34
51
elseif #args == 1 then -- :GoImpl io.Reader
35
-
recv = string.lower(recv) .. " *" .. recv
36
-
vim.cmd "redraw!"
37
-
iface = select(1, ...)
52
+
local st = ts_utils.get_struct_under_cursor(bufnr)
53
+
iface = args[1]
54
+
recv = string.lower(st.name) .. " *" .. st.name
38
55
elseif #args == 2 then -- :GoImpl w io.Writer
39
-
recv_name = select(1, ...)
40
-
recv = string.format("%s *%s", recv_name, recv)
41
-
iface = select(#args, ...)
42
-
elseif #args > 2 then
43
-
iface = select(#args, ...)
44
-
recv = select(#args - 1, ...)
45
-
recv_name = select(#args - 2, ...)
46
-
recv = string.format("%s %s", recv_name, recv)
56
+
local st = ts_utils.get_struct_under_cursor(bufnr)
57
+
iface = args[2]
58
+
recv = args[1] .. " *" .. st.name
59
+
elseif #args == 3 then -- :GoImpl r Struct io.Reader
60
+
recv = args[1] .. " *" .. args[2]
61
+
iface = args[3]
47
62
end
48
63
49
-
-- stylua: ignore
50
-
local cmd_args = {
51
-
"-dir", vim.fn.fnameescape(vim.fn.expand "%:p:h"), ---@diagnostic disable-line: missing-parameter
52
-
recv,
53
-
iface
54
-
}
64
+
assert(iface ~= "", "interface not provided")
65
+
assert(recv ~= "", "receiver not provided")
55
66
56
-
local res_data
57
-
Job
58
-
:new({
59
-
command = c.impl,
60
-
args = cmd_args,
61
-
on_exit = function(data, retval)
62
-
if retval ~= 0 then
63
-
u.notify("command 'impl " .. unpack(cmd_args) .. "' exited with code " .. retval, "error")
64
-
return
65
-
end
67
+
local dir = vim.fn.fnameescape(vim.fn.expand "%:p:h")
68
+
local rs = r.sync { c.impl, "-dir", dir, recv, iface }
69
+
if rs.code ~= 0 then
70
+
error("failed to implement interface: " .. rs.stderr)
71
+
end
66
72
67
-
res_data = data:result()
68
-
end,
69
-
})
70
-
:sync()
73
+
local pos = vim.fn.getcurpos()[2]
74
+
local output = u.remove_empty_lines(vim.split(rs.stdout, "\n"))
71
75
72
-
local pos = vim.fn.getcurpos()[2]
73
-
table.insert(res_data, 1, "")
74
-
vim.fn.append(pos, res_data)
76
+
table.insert(output, 1, "")
77
+
vim.fn.append(pos, output)
75
78
end
79
+
80
+
return impl
+61
-13
lua/gopher/init.lua
+61
-13
lua/gopher/init.lua
···
1
+
--- *gopher.nvim* Enhance your golang experience
2
+
---
3
+
--- MIT License Copyright (c) 2025 Oleksandr Smirnov
4
+
---
5
+
--- ==============================================================================
6
+
---
7
+
--- gopher.nvim is a minimalistic plugin for Go development in Neovim written in Lua.
8
+
--- It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim.
9
+
---
10
+
--- Table of Contents
11
+
---@toc
12
+
13
+
local log = require "gopher._utils.log"
1
14
local tags = require "gopher.struct_tags"
2
-
local gotests = require "gopher.gotests"
15
+
local tests = require "gopher.gotests"
16
+
local go = require "gopher.go"
3
17
local gopher = {}
4
18
5
-
gopher.install_deps = require "gopher.installer"
6
-
gopher.tags_add = tags.add
7
-
gopher.tags_rm = tags.remove
8
-
gopher.mod = require "gopher.gomod"
9
-
gopher.get = require "gopher.goget"
10
-
gopher.impl = require "gopher.impl"
11
-
gopher.generate = require "gopher.gogenerate"
12
-
gopher.comment = require "gopher.comment"
13
-
gopher.test_add = gotests.func_test
14
-
gopher.test_exported = gotests.all_exported_tests
15
-
gopher.tests_all = gotests.all_tests
16
-
gopher.setup = require("gopher.config").setup
19
+
---@toc_entry Setup
20
+
---@tag gopher.nvim-setup()
21
+
---@text Setup function. This method simply merges default config with opts table.
22
+
--- You can read more about configuration at |gopher.nvim-config|
23
+
--- Calling this function is optional, if you ok with default settings.
24
+
--- See |gopher.nvim.config|
25
+
---
26
+
---@usage >lua
27
+
--- require("gopher").setup {} -- use default config or replace {} with your own
28
+
--- <
29
+
---@param user_config gopher.Config See |gopher.nvim-config|
30
+
gopher.setup = function(user_config)
31
+
log.debug "setting up config"
32
+
require("gopher.config").setup(user_config)
33
+
log.debug(vim.inspect(user_config))
34
+
end
35
+
36
+
---@toc_entry Install dependencies
37
+
---@tag gopher.nvim-dependencies
38
+
---@text
39
+
--- Gopher.nvim implements most of its features using third-party tools. To
40
+
--- install plugin's dependencies, you can run:
41
+
--- `:GoInstallDeps` or `:GoInstallDepsSync`
42
+
--- or use `require("gopher").install_deps()` if you prefer lua api.
43
+
gopher.install_deps = require("gopher.installer").install_deps
44
+
45
+
gopher.impl = require("gopher.impl").impl
46
+
gopher.iferr = require("gopher.iferr").iferr
47
+
gopher.comment = require("gopher.comment").comment
48
+
49
+
gopher.tags = {
50
+
add = tags.add,
51
+
rm = tags.remove,
52
+
clear = tags.clear,
53
+
}
54
+
55
+
gopher.test = {
56
+
add = tests.func_test,
57
+
exported = tests.all_exported_tests,
58
+
all = tests.all_tests,
59
+
}
60
+
61
+
gopher.get = go.get
62
+
gopher.mod = go.mod
63
+
gopher.work = go.work
64
+
gopher.generate = go.generate
17
65
18
66
return gopher
+55
-23
lua/gopher/installer.lua
+55
-23
lua/gopher/installer.lua
···
1
-
local Job = require "plenary.job"
1
+
local c = require "gopher.config"
2
+
local r = require "gopher._utils.runner"
2
3
local u = require "gopher._utils"
4
+
local log = require "gopher._utils.log"
5
+
local installer = {}
6
+
3
7
local urls = {
4
-
gomodifytags = "github.com/fatih/gomodifytags",
5
-
impl = "github.com/josharian/impl",
6
-
gotests = "github.com/cweill/gotests/...",
8
+
gomodifytags = "github.com/fatih/gomodifytags@latest",
9
+
impl = "github.com/josharian/impl@latest",
10
+
gotests = "github.com/cweill/gotests/...@develop",
11
+
iferr = "github.com/koron/iferr@latest",
12
+
json2go = "olexsmir.xyz/json2go/cmd/json2go@latest",
7
13
}
8
14
9
-
---@param pkg string
10
-
local function install(pkg)
11
-
local url = urls[pkg] .. "@latest"
15
+
---@param opt vim.SystemCompleted
16
+
---@param url string
17
+
local function handle_intall_exit(opt, url)
18
+
if opt.code ~= 0 then
19
+
vim.schedule(function()
20
+
u.notify("go install failed: " .. url)
21
+
end)
12
22
13
-
Job
14
-
:new({
15
-
command = "go",
16
-
args = { "install", url },
17
-
on_exit = function(_, retval)
18
-
if retval ~= 0 then
19
-
u.notify("command 'go install " .. url .. "' exited with code " .. retval, "error")
20
-
return
21
-
end
23
+
log.error("go install failed:", "url", url, "opt", vim.inspect(opt))
24
+
return
25
+
end
22
26
23
-
u.notify("install " .. url .. " finished", "info ")
24
-
end,
25
-
})
26
-
:start()
27
+
vim.schedule(function()
28
+
u.notify("go install-ed: " .. url)
29
+
end)
30
+
end
31
+
32
+
---@param url string
33
+
local function install(url)
34
+
vim.schedule(function()
35
+
u.notify("go install-ing: " .. url)
36
+
end)
37
+
38
+
r.async({ c.commands.go, "install", url }, function(opt)
39
+
handle_intall_exit(opt, url)
40
+
end, { timeout = c.installer_timeout })
41
+
end
42
+
43
+
---@param url string
44
+
local function install_sync(url)
45
+
vim.schedule(function()
46
+
u.notify("go install-ing: " .. url)
47
+
end)
48
+
49
+
local rs = r.sync({ c.commands.go, "install", url }, { timeout = c.installer_timeout })
50
+
handle_intall_exit(rs, url)
27
51
end
28
52
29
53
---Install required go deps
30
-
return function()
31
-
for pkg, _ in pairs(urls) do
32
-
install(pkg)
54
+
---@param opts? {sync:boolean}
55
+
function installer.install_deps(opts)
56
+
opts = opts or {}
57
+
for _, url in pairs(urls) do
58
+
if opts.sync then
59
+
install_sync(url)
60
+
else
61
+
install(url)
62
+
end
33
63
end
34
64
end
65
+
66
+
return installer
+137
lua/gopher/json2go.lua
+137
lua/gopher/json2go.lua
···
1
+
---@toc_entry json2go
2
+
---@tag gopher.nvim-json2go
3
+
---@text
4
+
--- Convert json to go type annotations.
5
+
---
6
+
---@usage
7
+
--- `:GoJson` opens a temporary buffer where you can paste or write JSON.
8
+
--- Saving the buffer (`:w` or `:wq`) automatically closes it and inserts the
9
+
--- generated Go struct into the original buffer at the cursor position.
10
+
---
11
+
--- Alternatively, you can pass JSON directly as an argument:
12
+
--- >vim
13
+
--- :GoJson {"name": "Alice", "age": 30}
14
+
--- <
15
+
16
+
local c = require "gopher.config"
17
+
local log = require "gopher._utils.log"
18
+
local u = require "gopher._utils"
19
+
local r = require "gopher._utils.runner"
20
+
local json2go = {}
21
+
22
+
---@dochide
23
+
---@param bufnr integer
24
+
---@param cpos integer
25
+
---@param type_ string
26
+
local function apply(bufnr, cpos, type_)
27
+
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
28
+
local input_lines = u.remove_empty_lines(vim.split(type_, "\n"))
29
+
for i, line in pairs(input_lines) do
30
+
table.insert(lines, cpos + i, line)
31
+
end
32
+
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines)
33
+
end
34
+
35
+
-- Convert json string to go type.
36
+
---
37
+
---@param json_str string Json string that is going to be converted to go type.
38
+
---@return string? Go type, or nil if failed.
39
+
function json2go.transform(json_str)
40
+
local cmd = { c.commands.json2go }
41
+
if c.json2go.type_name then
42
+
table.insert(cmd, "-type", c.json2go.type_name)
43
+
end
44
+
45
+
local rs = r.sync(cmd, { stdin = json_str })
46
+
if rs.code ~= 0 then
47
+
u.notify("json2go: got this error: " .. rs.stdout, vim.log.levels.ERROR)
48
+
log.error("json2go: got this error: " .. rs.stdout)
49
+
return
50
+
end
51
+
return rs.stdout
52
+
end
53
+
54
+
---@dochide
55
+
---@param ocpos integer
56
+
local function interactive(ocpos)
57
+
local obuf = vim.api.nvim_get_current_buf()
58
+
local owin = vim.api.nvim_get_current_win()
59
+
60
+
-- setup the input window
61
+
local buf = vim.api.nvim_create_buf(false, true)
62
+
vim.cmd(c.json2go.interactive_cmd)
63
+
64
+
local win = vim.api.nvim_get_current_win()
65
+
vim.api.nvim_win_set_buf(win, buf)
66
+
vim.api.nvim_buf_set_name(buf, "[GoJson input]")
67
+
vim.api.nvim_set_option_value("filetype", "jsonc", { buf = buf })
68
+
vim.api.nvim_set_option_value("buftype", "acwrite", { buf = buf })
69
+
vim.api.nvim_set_option_value("swapfile", false, { buf = buf })
70
+
vim.api.nvim_set_option_value("bufhidden", "delete", { buf = buf })
71
+
vim.api.nvim_buf_set_lines(buf, 0, -1, false, {
72
+
"// Write your json here.",
73
+
"// Writing and quitting (:wq), will generate go struct under the cursor.",
74
+
"",
75
+
"",
76
+
})
77
+
78
+
vim.api.nvim_create_autocmd("BufLeave", { buffer = buf, command = "stopinsert" })
79
+
vim.api.nvim_create_autocmd("BufWriteCmd", {
80
+
buffer = buf,
81
+
once = true,
82
+
callback = function()
83
+
local input = vim.api.nvim_buf_get_lines(buf, 0, -1, true)
84
+
local inp = table.concat(
85
+
vim
86
+
.iter(input)
87
+
:filter(function(line)
88
+
local found = string.find(line, "^//.*")
89
+
return (not found) or (line == "")
90
+
end)
91
+
:totable(),
92
+
"\n"
93
+
)
94
+
95
+
local go_type = json2go.transform(inp)
96
+
if not go_type then
97
+
error "cound't convert json to go type"
98
+
end
99
+
100
+
vim.api.nvim_set_option_value("modified", false, { buf = buf })
101
+
apply(obuf, ocpos, go_type)
102
+
103
+
vim.api.nvim_set_current_win(owin)
104
+
vim.api.nvim_win_set_cursor(owin, { ocpos + 1, 0 })
105
+
106
+
vim.schedule(function()
107
+
pcall(vim.api.nvim_win_close, win, true)
108
+
pcall(vim.api.nvim_buf_delete, buf, {})
109
+
end)
110
+
end,
111
+
})
112
+
113
+
vim.cmd "normal! G"
114
+
vim.cmd "startinsert"
115
+
end
116
+
117
+
--- Converts json string to go type, and puts result under the cursor. If
118
+
--- [json_str] is nil, will open an interactive prompt (with cmd set in
119
+
--- config).
120
+
---
121
+
---@param json_str? string
122
+
function json2go.json2go(json_str)
123
+
local cur_line = vim.api.nvim_win_get_cursor(0)[1]
124
+
if not json_str then
125
+
interactive(cur_line)
126
+
return
127
+
end
128
+
129
+
local go_type = json2go.transform(json_str)
130
+
if not go_type then
131
+
error "cound't convert json to go type"
132
+
end
133
+
134
+
apply(0, cur_line, go_type)
135
+
end
136
+
137
+
return json2go
+1
-38
nvim.toml
+1
-38
nvim.toml
···
1
1
[vim]
2
2
any = true
3
3
4
-
[describe]
5
-
any = true
6
-
[[describe.args]]
7
-
type = "string"
8
-
[[describe.args]]
9
-
type = "function"
10
-
11
-
[it]
12
-
any = true
13
-
[[it.args]]
14
-
type = "string"
15
-
[[it.args]]
16
-
type = "function"
17
-
18
-
[before_each]
4
+
[MiniTest]
19
5
any = true
20
-
[[before_each.args]]
21
-
type = "function"
22
-
[[after_each.args]]
23
-
type = "function"
24
-
25
-
[assert]
26
-
any = true
27
-
28
-
[assert.is_not]
29
-
any = true
30
-
31
-
[[assert.equals.args]]
32
-
type = "any"
33
-
[[assert.equals.args]]
34
-
type = "any"
35
-
[[assert.equals.args]]
36
-
type = "any"
37
-
required = false
38
-
39
-
[[assert.same.args]]
40
-
type = "any"
41
-
[[assert.same.args]]
42
-
type = "any"
+10
pkg.json
+10
pkg.json
+107
plugin/gopher.lua
+107
plugin/gopher.lua
···
1
+
---@toc_entry Commands
2
+
---@tag gopher.nvim-commands
3
+
---@text
4
+
--- If don't want to automatically register plugins' commands,
5
+
--- you can set `vim.g.gopher_register_commands` to `false`, before loading the plugin.
6
+
7
+
if vim.g.gopher_register_commands == false then
8
+
return
9
+
end
10
+
11
+
---@param name string
12
+
---@param fn fun(args: table)
13
+
---@param nargs? number|"*"|"?"
14
+
---@param range? boolean
15
+
---@private
16
+
local function cmd(name, fn, nargs, range)
17
+
vim.api.nvim_create_user_command(name, fn, {
18
+
nargs = nargs or 0,
19
+
range = range or false,
20
+
})
21
+
end
22
+
23
+
cmd("GopherLog", function()
24
+
vim.cmd("tabnew " .. require("gopher._utils.log").get_outfile())
25
+
end)
26
+
27
+
cmd("GoIfErr", function()
28
+
require("gopher").iferr()
29
+
end)
30
+
31
+
cmd("GoCmt", function()
32
+
require("gopher").comment()
33
+
end)
34
+
35
+
cmd("GoImpl", function(args)
36
+
require("gopher").impl(unpack(args.fargs))
37
+
end, "*")
38
+
39
+
-- :GoInstall
40
+
cmd("GoInstallDeps", function()
41
+
require("gopher").install_deps()
42
+
end)
43
+
44
+
cmd("GoInstallDepsSync", function()
45
+
require("gopher").install_deps { sync = true }
46
+
end)
47
+
48
+
-- :GoTag
49
+
cmd("GoTagAdd", function(opts)
50
+
require("gopher").tags.add {
51
+
input = opts.fargs,
52
+
range = (opts.count ~= -1) and {
53
+
start = opts.line1,
54
+
end_ = opts.line2,
55
+
} or nil,
56
+
}
57
+
end, "*", true)
58
+
59
+
cmd("GoTagRm", function(opts)
60
+
require("gopher").tags.rm {
61
+
input = opts.fargs,
62
+
range = (opts.count ~= -1) and {
63
+
start = opts.line1,
64
+
end_ = opts.line2,
65
+
} or nil,
66
+
}
67
+
end, "*", true)
68
+
69
+
cmd("GoTagClear", function()
70
+
require("gopher").tags.clear()
71
+
end)
72
+
73
+
-- :GoJson
74
+
cmd("GoJson", function(opts)
75
+
local inp = ((opts.args ~= "" and opts.args) or nil)
76
+
require("gopher.json2go").json2go(inp)
77
+
end, "*")
78
+
79
+
-- :GoTest
80
+
cmd("GoTestAdd", function()
81
+
require("gopher").test.add()
82
+
end)
83
+
84
+
cmd("GoTestsAll", function()
85
+
require("gopher").test.all()
86
+
end)
87
+
88
+
cmd("GoTestsExp", function()
89
+
require("gopher").test.exported()
90
+
end)
91
+
92
+
-- :Go
93
+
cmd("GoMod", function(opts)
94
+
require("gopher").mod(opts.fargs)
95
+
end, "*")
96
+
97
+
cmd("GoGet", function(opts)
98
+
require("gopher").get(opts.fargs)
99
+
end, "*")
100
+
101
+
cmd("GoWork", function(opts)
102
+
require("gopher").get(opts.fargs)
103
+
end, "*")
104
+
105
+
cmd("GoGenerate", function(opts)
106
+
require("gopher").generate(opts.fargs or { "" })
107
+
end, "?")
-11
plugin/gopher.vim
-11
plugin/gopher.vim
···
1
-
command! -nargs=* GoTagAdd :lua require"gopher".tags_add(<f-args>)
2
-
command! -nargs=* GoTagRm :lua require"gopher".tags_rm(<f-args>)
3
-
command! -nargs=* GoTestAdd :lua require"gopher".test_add(<f-args>)
4
-
command! -nargs=* GoTestsAll :lua require"gopher".tests_all(<f-args>)
5
-
command! -nargs=* GoTestsExp :lua require"gopher".test_exported(<f-args>)
6
-
command! -nargs=* GoMod :lua require"gopher".mod(<f-args>)
7
-
command! -nargs=* GoGet :lua require"gopher".get(<f-args>)
8
-
command! -nargs=* GoImpl :lua require"gopher".impl(<f-args>)
9
-
command! -nargs=* GoGenerate :lua require"gopher".generate(<f-args>)
10
-
command! GoCmt :lua require"gopher".comment()
11
-
command! GoInstallDeps :lua require"gopher".install_deps()
+38
scripts/docgen.lua
+38
scripts/docgen.lua
···
1
+
---@diagnostic disable: undefined-global
2
+
--# selene: allow(undefined_variable)
3
+
4
+
local okay, minidoc = pcall(require, "mini.doc")
5
+
if not okay then
6
+
error "mini.doc not found, please install it. https://github.com/echasnovski/mini.doc"
7
+
return
8
+
end
9
+
10
+
local files = {
11
+
"lua/gopher/init.lua",
12
+
"lua/gopher/config.lua",
13
+
"plugin/gopher.lua",
14
+
"lua/gopher/struct_tags.lua",
15
+
"lua/gopher/json2go.lua",
16
+
"lua/gopher/impl.lua",
17
+
"lua/gopher/gotests.lua",
18
+
"lua/gopher/iferr.lua",
19
+
"lua/gopher/comment.lua",
20
+
}
21
+
22
+
minidoc.setup()
23
+
24
+
local hooks = vim.deepcopy(minidoc.default_hooks)
25
+
hooks.write_pre = function(lines)
26
+
-- Remove first two lines with `======` and `------` delimiters to comply
27
+
-- with `:h local-additions` template
28
+
table.remove(lines, 1)
29
+
table.remove(lines, 1)
30
+
31
+
return lines
32
+
end
33
+
34
+
hooks.sections["@dochide"] = function(s)
35
+
s.parent:clear_lines()
36
+
end
37
+
38
+
MiniDoc.generate(files, "doc/gopher.nvim.txt", { hooks = hooks })
+72
scripts/minimal_init.lua
+72
scripts/minimal_init.lua
···
1
+
local function root(p)
2
+
local f = debug.getinfo(1, "S").source:sub(2)
3
+
return vim.fn.fnamemodify(f, ":p:h:h") .. "/" .. (p or "")
4
+
end
5
+
6
+
local function install_plug(plugin)
7
+
local name = plugin:match ".*/(.*)"
8
+
local package_root = root ".tests/site/pack/deps/start/"
9
+
if not vim.uv.fs_stat(package_root .. name) then
10
+
print("Installing " .. plugin)
11
+
vim
12
+
.system({
13
+
"git",
14
+
"clone",
15
+
"--depth=1",
16
+
"https://github.com/" .. plugin .. ".git",
17
+
package_root .. "/" .. name,
18
+
})
19
+
:wait()
20
+
end
21
+
end
22
+
23
+
install_plug "nvim-treesitter/nvim-treesitter"
24
+
install_plug "echasnovski/mini.doc" -- used for docs generation
25
+
install_plug "folke/tokyonight.nvim" -- theme for generating demos
26
+
install_plug "echasnovski/mini.test"
27
+
28
+
vim.env.XDG_CONFIG_HOME = root ".tests/config"
29
+
vim.env.XDG_DATA_HOME = root ".tests/data"
30
+
vim.env.XDG_STATE_HOME = root ".tests/state"
31
+
vim.env.XDG_CACHE_HOME = root ".tests/cache"
32
+
33
+
vim.opt.runtimepath:append(root())
34
+
vim.opt.packpath:append(root ".tests/site")
35
+
vim.o.swapfile = false
36
+
vim.o.writebackup = false
37
+
vim.notify = vim.print
38
+
39
+
-- install go treesitter parse
40
+
require("nvim-treesitter.install").ensure_installed_sync "go"
41
+
42
+
require("gopher").setup {
43
+
log_level = vim.log.levels.OFF,
44
+
timeout = 4000,
45
+
}
46
+
47
+
-- setup mini.test only when running headless nvim
48
+
if #vim.api.nvim_list_uis() == 0 then
49
+
require("mini.test").setup {
50
+
collect = {
51
+
find_files = function()
52
+
return vim.fn.globpath("spec", "**/*_test.lua", true, true)
53
+
end,
54
+
},
55
+
}
56
+
end
57
+
58
+
-- set colorscheme only when running ui
59
+
if #vim.api.nvim_list_uis() == 1 then
60
+
vim.cmd.colorscheme "tokyonight-night"
61
+
vim.o.cursorline = true
62
+
vim.o.number = true
63
+
end
64
+
65
+
-- needed for tests, i dont know the reason why, but on start
66
+
-- vim is not able to use treesitter for go by default
67
+
vim.api.nvim_create_autocmd("FileType", {
68
+
pattern = "go",
69
+
callback = function(args)
70
+
vim.treesitter.start(args.buf, "go")
71
+
end,
72
+
})
spec/fixtures/comment/empty_input.go
spec/fixtures/comment/empty_input.go
This is a binary file and will not be displayed.
+5
spec/fixtures/comment/func_input.go
+5
spec/fixtures/comment/func_input.go
+6
spec/fixtures/comment/func_output.go
+6
spec/fixtures/comment/func_output.go
+6
spec/fixtures/comment/interface_many_method_input.go
+6
spec/fixtures/comment/interface_many_method_input.go
+7
spec/fixtures/comment/interface_many_method_output.go
+7
spec/fixtures/comment/interface_many_method_output.go
+5
spec/fixtures/comment/interface_method_input.go
+5
spec/fixtures/comment/interface_method_input.go
+6
spec/fixtures/comment/interface_method_output.go
+6
spec/fixtures/comment/interface_method_output.go
+4
spec/fixtures/comment/interface_output.go
+4
spec/fixtures/comment/interface_output.go
+18
spec/fixtures/comment/many_structs_fields_input.go
+18
spec/fixtures/comment/many_structs_fields_input.go
+19
spec/fixtures/comment/many_structs_fields_output.go
+19
spec/fixtures/comment/many_structs_fields_output.go
+7
spec/fixtures/comment/method_input.go
+7
spec/fixtures/comment/method_input.go
+8
spec/fixtures/comment/method_output.go
+8
spec/fixtures/comment/method_output.go
+1
spec/fixtures/comment/package_input.go
+1
spec/fixtures/comment/package_input.go
···
1
+
package main
+7
spec/fixtures/comment/struct_fields_input.go
+7
spec/fixtures/comment/struct_fields_input.go
+8
spec/fixtures/comment/struct_fields_output.go
+8
spec/fixtures/comment/struct_fields_output.go
+4
spec/fixtures/comment/struct_output.go
+4
spec/fixtures/comment/struct_output.go
+5
spec/fixtures/comment/svar_input.go
+5
spec/fixtures/comment/svar_input.go
+6
spec/fixtures/comment/svar_output.go
+6
spec/fixtures/comment/svar_output.go
+5
spec/fixtures/comment/var_input.go
+5
spec/fixtures/comment/var_input.go
+6
spec/fixtures/comment/var_output.go
+6
spec/fixtures/comment/var_output.go
+8
spec/fixtures/comment/var_struct_fields_input.go
+8
spec/fixtures/comment/var_struct_fields_input.go
+9
spec/fixtures/comment/var_struct_fields_output.go
+9
spec/fixtures/comment/var_struct_fields_output.go
+9
spec/fixtures/iferr/iferr_input.go
+9
spec/fixtures/iferr/iferr_input.go
+12
spec/fixtures/iferr/iferr_output.go
+12
spec/fixtures/iferr/iferr_output.go
+7
spec/fixtures/iferr/message_input.go
+7
spec/fixtures/iferr/message_input.go
+10
spec/fixtures/iferr/message_output.go
+10
spec/fixtures/iferr/message_output.go
+7
spec/fixtures/impl/closer_output.go
+7
spec/fixtures/impl/closer_output.go
+7
spec/fixtures/impl/reader_output.go
+7
spec/fixtures/impl/reader_output.go
+7
spec/fixtures/impl/writer_output.go
+7
spec/fixtures/impl/writer_output.go
+5
spec/fixtures/json2go/interativly_output.go
+5
spec/fixtures/json2go/interativly_output.go
+7
spec/fixtures/json2go/manual_output.go
+7
spec/fixtures/json2go/manual_output.go
+5
spec/fixtures/tests/function_input.go
+5
spec/fixtures/tests/function_input.go
+24
spec/fixtures/tests/function_output.go
+24
spec/fixtures/tests/function_output.go
···
1
+
package fortest
2
+
3
+
import "testing"
4
+
5
+
func TestAdd(t *testing.T) {
6
+
type args struct {
7
+
x int
8
+
y int
9
+
}
10
+
tests := []struct {
11
+
name string
12
+
args args
13
+
want int
14
+
}{
15
+
// TODO: Add test cases.
16
+
}
17
+
for _, tt := range tests {
18
+
t.Run(tt.name, func(t *testing.T) {
19
+
if got := Add(tt.args.x, tt.args.y); got != tt.want {
20
+
t.Errorf("Add() = %v, want %v", got, tt.want)
21
+
}
22
+
})
23
+
}
24
+
}
+7
spec/fixtures/tests/method_input.go
+7
spec/fixtures/tests/method_input.go
+26
spec/fixtures/tests/method_output.go
+26
spec/fixtures/tests/method_output.go
···
1
+
package fortest
2
+
3
+
import "testing"
4
+
5
+
func TestForTest_Add(t *testing.T) {
6
+
type args struct {
7
+
x int
8
+
y int
9
+
}
10
+
tests := []struct {
11
+
name string
12
+
tr *ForTest
13
+
args args
14
+
want int
15
+
}{
16
+
// TODO: Add test cases.
17
+
}
18
+
for _, tt := range tests {
19
+
t.Run(tt.name, func(t *testing.T) {
20
+
tr := &ForTest{}
21
+
if got := tr.Add(tt.args.x, tt.args.y); got != tt.want {
22
+
t.Errorf("ForTest.Add() = %v, want %v", got, tt.want)
23
+
}
24
+
})
25
+
}
26
+
}
-41
spec/gopher_config_spec.lua
-41
spec/gopher_config_spec.lua
···
1
-
describe("gopher.config", function()
2
-
it("can be required", function()
3
-
require "gopher.config"
4
-
end)
5
-
6
-
it(".setup() when gets empty table not edit config", function()
7
-
local c = require "gopher.config"
8
-
c.setup {}
9
-
10
-
assert.are.same(c.config.commands.go, "go")
11
-
assert.are.same(c.config.commands.gomodifytags, "gomodifytags")
12
-
assert.are.same(c.config.commands.gotests, "gotests")
13
-
assert.are.same(c.config.commands.impl, "impl")
14
-
end)
15
-
16
-
it(".setup() when get one custom value sets that", function()
17
-
local c = require "gopher.config"
18
-
c.setup { commands = {
19
-
go = "custom_go",
20
-
} }
21
-
22
-
assert.are.same(c.config.commands.go, "custom_go")
23
-
end)
24
-
25
-
it(".setup() when get all custom values sets it", function()
26
-
local c = require "gopher.config"
27
-
c.setup {
28
-
commands = {
29
-
go = "go1.18",
30
-
gomodifytags = "user-gomodifytags",
31
-
gotests = "gotests",
32
-
impl = "goimpl",
33
-
},
34
-
}
35
-
36
-
assert.are.same(c.config.commands.go, "go1.18")
37
-
assert.are.same(c.config.commands.gomodifytags, "user-gomodifytags")
38
-
assert.are.same(c.config.commands.gotests, "gotests")
39
-
assert.are.same(c.config.commands.impl, "goimpl")
40
-
end)
41
-
end)
-5
spec/gopher_spec.lua
-5
spec/gopher_spec.lua
-19
spec/gopher_utils_spec.lua
-19
spec/gopher_utils_spec.lua
···
1
-
describe("gopher._utils", function()
2
-
it("can be requried", function()
3
-
require "gopher._utils"
4
-
end)
5
-
6
-
it(".empty() with non-empty talbe", function()
7
-
local empty = require("gopher._utils").empty
8
-
local res = empty { first = "1", second = 2 }
9
-
10
-
assert.are.same(res, false)
11
-
end)
12
-
13
-
it(".empty() with empty talbe", function()
14
-
local empty = require("gopher._utils").empty
15
-
local res = empty {}
16
-
17
-
assert.are.same(res, true)
18
-
end)
19
-
end)
+65
spec/integration/comment_test.lua
+65
spec/integration/comment_test.lua
···
1
+
local t = require "spec.testutils"
2
+
local child, T, comment = t.setup "comment"
3
+
4
+
local function do_the_test(fixture, pos)
5
+
local rs = t.setup_test("comment/" .. fixture, child, pos)
6
+
child.cmd "GoCmt"
7
+
child.cmd "write"
8
+
9
+
t.eq(t.readfile(rs.tmp), rs.fixtures.output)
10
+
t.cleanup(rs)
11
+
end
12
+
13
+
comment["should add comment to package"] = function()
14
+
do_the_test("package", { 1, 1 })
15
+
end
16
+
17
+
comment["should add comment to struct"] = function()
18
+
do_the_test("struct", { 4, 1 })
19
+
end
20
+
21
+
comment["should add a comment on struct field"] = function()
22
+
do_the_test("struct_fields", { 5, 8 })
23
+
end
24
+
25
+
comment["should add a comment on var struct field"] = function()
26
+
do_the_test("var_struct_fields", { 6, 4 })
27
+
end
28
+
29
+
comment["should add a comment on one field of many structs"] = function()
30
+
do_the_test("many_structs_fields", { 10, 4 })
31
+
end
32
+
33
+
comment["should add comment to function"] = function()
34
+
do_the_test("func", { 3, 1 })
35
+
end
36
+
37
+
comment["should add comment to method"] = function()
38
+
do_the_test("method", { 5, 1 })
39
+
end
40
+
41
+
comment["should add comment to interface"] = function()
42
+
do_the_test("interface", { 3, 6 })
43
+
end
44
+
45
+
comment["should add comment on interface method"] = function()
46
+
do_the_test("interface_method", { 4, 2 })
47
+
end
48
+
49
+
comment["should add a comment on interface with many method"] = function()
50
+
do_the_test("interface_many_method", { 5, 2 })
51
+
end
52
+
53
+
comment["should add a comment on a var"] = function()
54
+
do_the_test("var", { 4, 2 })
55
+
end
56
+
57
+
comment["should add a comment on a short declared var"] = function()
58
+
do_the_test("svar", { 4, 8 })
59
+
end
60
+
61
+
comment["otherwise should add // above cursor"] = function()
62
+
do_the_test("empty", { 1, 1 })
63
+
end
64
+
65
+
return T
+29
spec/integration/gotests_test.lua
+29
spec/integration/gotests_test.lua
···
1
+
local t = require "spec.testutils"
2
+
local child, T, gotests = t.setup "gotests"
3
+
4
+
--- NOTE: :GoTestAdd is the only place that has actual logic
5
+
--- All other parts are handled `gotests` itself.
6
+
7
+
---@param fpath string
8
+
---@return string
9
+
local function read_testfile(fpath)
10
+
return t.readfile(fpath:gsub(".go", "_test.go"))
11
+
end
12
+
13
+
gotests["should add test for function under cursor"] = function()
14
+
local rs = t.setup_test("tests/function", child, { 3, 5 })
15
+
child.cmd "GoTestAdd"
16
+
17
+
t.eq(rs.fixtures.output, read_testfile(rs.tmp))
18
+
t.cleanup(rs)
19
+
end
20
+
21
+
gotests["should add test for method under cursor"] = function()
22
+
local rs = t.setup_test("tests/method", child, { 5, 19 })
23
+
child.cmd "GoTestAdd"
24
+
25
+
t.eq(rs.fixtures.output, read_testfile(rs.tmp))
26
+
t.cleanup(rs)
27
+
end
28
+
29
+
return T
+28
spec/integration/iferr_test.lua
+28
spec/integration/iferr_test.lua
···
1
+
local t = require "spec.testutils"
2
+
local child, T, iferr = t.setup "iferr"
3
+
4
+
iferr["should add if != nil {"] = function()
5
+
local rs = t.setup_test("iferr/iferr", child, { 8, 2 })
6
+
child.cmd "GoIfErr"
7
+
child.cmd "write"
8
+
9
+
t.eq(t.readfile(rs.tmp), rs.fixtures.output)
10
+
t.cleanup(rs)
11
+
end
12
+
13
+
iferr["should add if err with custom message"] = function()
14
+
child.lua [[
15
+
require("gopher").setup {
16
+
iferr = { message = 'fmt.Errorf("failed to %w", err)' }
17
+
}
18
+
]]
19
+
20
+
local rs = t.setup_test("iferr/message", child, { 6, 2 })
21
+
child.cmd "GoIfErr"
22
+
child.cmd "write"
23
+
24
+
t.eq(t.readfile(rs.tmp), rs.fixtures.output)
25
+
t.cleanup(rs)
26
+
end
27
+
28
+
return T
+35
spec/integration/impl_test.lua
+35
spec/integration/impl_test.lua
···
1
+
local t = require "spec.testutils"
2
+
local child, T, impl = t.setup "impl"
3
+
4
+
impl["should do impl with 'w io.Writer'"] = function()
5
+
local rs = t.setup_test("impl/writer", child, { 3, 0 })
6
+
child.cmd "GoImpl w io.Writer"
7
+
child.cmd "write"
8
+
9
+
-- NOTE: since "impl" won't implement interface if it's already implemented i went with this hack
10
+
local rhs = rs.fixtures.output:gsub("Test2", "Test")
11
+
t.eq(t.readfile(rs.tmp), rhs)
12
+
t.cleanup(rs)
13
+
end
14
+
15
+
impl["should work with full input, 'r Read io.Reader'"] = function()
16
+
local rs = t.setup_test("impl/reader", child)
17
+
child.cmd "GoImpl r Read io.Reader"
18
+
child.cmd "write"
19
+
20
+
local rhs = rs.fixtures.output:gsub("Read2", "Read")
21
+
t.eq(t.readfile(rs.tmp), rhs)
22
+
t.cleanup(rs)
23
+
end
24
+
25
+
impl["should work with minimal input 'io.Closer'"] = function()
26
+
local rs = t.setup_test("impl/closer", child, { 3, 6 })
27
+
child.cmd "GoImpl io.Closer"
28
+
child.cmd "write"
29
+
30
+
local rhs = rs.fixtures.output:gsub("Test2", "Test")
31
+
t.eq(t.readfile(rs.tmp), rhs)
32
+
t.cleanup(rs)
33
+
end
34
+
35
+
return T
+25
spec/integration/json2go_test.lua
+25
spec/integration/json2go_test.lua
···
1
+
local t = require "spec.testutils"
2
+
local child, T, json2go = t.setup "json2go"
3
+
4
+
json2go["should convert interativly"] = function()
5
+
local rs = t.setup_test("json2go/interativly", child, { 2, 0 })
6
+
child.cmd "GoJson"
7
+
child.type_keys [[{"json": true}]]
8
+
child.type_keys "<Esc>"
9
+
child.cmd "wq" -- quit prompt
10
+
child.cmd "write" -- the fixture file
11
+
12
+
t.eq(t.readfile(rs.tmp), rs.fixtures.output)
13
+
t.cleanup(rs)
14
+
end
15
+
16
+
json2go["should convert argument"] = function()
17
+
local rs = t.setup_test("json2go/manual", child, { 2, 0 })
18
+
child.cmd [[GoJson {"user": {"name": "user-ovic"}}]]
19
+
child.cmd "write"
20
+
21
+
t.eq(t.readfile(rs.tmp), rs.fixtures.output)
22
+
t.cleanup(rs)
23
+
end
24
+
25
+
return T
+108
spec/testutils.lua
+108
spec/testutils.lua
···
1
+
local base_dir = vim.env.GOPHER_DIR or vim.fn.expand "%:p:h"
2
+
3
+
---@class gopher.TestUtils
4
+
local testutils = {}
5
+
6
+
testutils.mininit_path = vim.fs.joinpath(base_dir, "scripts", "minimal_init.lua")
7
+
testutils.fixtures_dir = vim.fs.joinpath(base_dir, "spec/fixtures")
8
+
9
+
---@param mod string Module name for which to create a nested test set.
10
+
---@return MiniTest.child child nvim client.
11
+
---@return table T root test set created by `MiniTest.new_set()`.
12
+
---@return table mod_name nested set of tests in `T[mod]`.
13
+
function testutils.setup(mod)
14
+
local child = MiniTest.new_child_neovim()
15
+
local T = MiniTest.new_set {
16
+
hooks = {
17
+
post_once = child.stop,
18
+
pre_case = function()
19
+
child.restart { "-u", testutils.mininit_path }
20
+
end,
21
+
},
22
+
}
23
+
24
+
T[mod] = MiniTest.new_set {}
25
+
return child, T, T[mod]
26
+
end
27
+
28
+
---@generic T
29
+
---@param a T
30
+
---@param b T
31
+
---@return boolean
32
+
function testutils.eq(a, b)
33
+
return MiniTest.expect.equality(a, b)
34
+
end
35
+
36
+
---@return string
37
+
function testutils.tmpfile()
38
+
return vim.fn.tempname() .. ".go"
39
+
end
40
+
41
+
---@param path string
42
+
---@return string
43
+
function testutils.readfile(path)
44
+
return vim.fn.join(vim.fn.readfile(path), "\n")
45
+
end
46
+
47
+
---@param fpath string
48
+
---@param contents string
49
+
function testutils.writefile(fpath, contents)
50
+
vim.fn.writefile(vim.split(contents, "\n"), fpath)
51
+
end
52
+
53
+
---@param fpath string
54
+
function testutils.deletefile(fpath)
55
+
vim.fn.delete(fpath)
56
+
end
57
+
58
+
---@class gopher.TestUtilsFixtures
59
+
---@field input string
60
+
---@field output string
61
+
62
+
---@param fixture string
63
+
---@return gopher.TestUtilsFixtures
64
+
function testutils.get_fixtures(fixture)
65
+
return {
66
+
input = testutils.readfile(vim.fs.joinpath(testutils.fixtures_dir, fixture) .. "_input.go"),
67
+
output = testutils.readfile(vim.fs.joinpath(testutils.fixtures_dir, fixture) .. "_output.go"),
68
+
}
69
+
end
70
+
71
+
---@class gopher.TestUtilsSetup
72
+
---@field tmp string
73
+
---@field fixtures gopher.TestUtilsFixtures
74
+
---@field bufnr number
75
+
76
+
---@param fixture string
77
+
---@param child MiniTest.child
78
+
---@param pos? number[]
79
+
---@return gopher.TestUtilsSetup
80
+
function testutils.setup_test(fixture, child, pos)
81
+
vim.validate("pos", pos, "table", true)
82
+
83
+
local tmp = testutils.tmpfile()
84
+
local fixtures = testutils.get_fixtures(fixture)
85
+
86
+
testutils.writefile(tmp, fixtures.input)
87
+
child.cmd("silent edit " .. tmp)
88
+
89
+
local bufnr = child.fn.bufnr(tmp)
90
+
if pos then
91
+
assert(#pos == 2, "invalid cursor position")
92
+
93
+
child.fn.setpos(".", { bufnr, unpack(pos) })
94
+
end
95
+
96
+
return {
97
+
tmp = tmp,
98
+
bufnr = bufnr,
99
+
fixtures = fixtures,
100
+
}
101
+
end
102
+
103
+
---@param inp gopher.TestUtilsSetup
104
+
function testutils.cleanup(inp)
105
+
testutils.deletefile(inp.tmp)
106
+
end
107
+
108
+
return testutils
+25
spec/unit/config_test.lua
+25
spec/unit/config_test.lua
···
1
+
local t = require "spec.testutils"
2
+
local _, T, config = t.setup "config"
3
+
4
+
config["can be called without any arguments passed"] = function()
5
+
---@diagnostic disable-next-line: missing-parameter
6
+
require("gopher").setup()
7
+
end
8
+
9
+
config["can be called with empty table"] = function()
10
+
---@diagnostic disable-next-line: missing-fields
11
+
require("gopher").setup {}
12
+
end
13
+
14
+
config["should change option"] = function()
15
+
local log_level = 1234567890
16
+
17
+
---@diagnostic disable-next-line: missing-fields
18
+
require("gopher").setup {
19
+
log_level = log_level,
20
+
}
21
+
22
+
t.eq(log_level, require("gopher.config").log_level)
23
+
end
24
+
25
+
return T
+68
spec/unit/struct_tag_test.lua
+68
spec/unit/struct_tag_test.lua
···
1
+
local t = require "spec.testutils"
2
+
local _, T, st = t.setup "struct_tags"
3
+
4
+
st["should parse tags"] = function()
5
+
local out = require("gopher.struct_tags").parse_args { "json", "yaml", "etc" }
6
+
7
+
t.eq(out.tags, "json,yaml,etc")
8
+
t.eq(out.options, "")
9
+
end
10
+
11
+
st["should parse tags separated by commas"] = function()
12
+
local out = require("gopher.struct_tags").parse_args { "json,yaml,etc" }
13
+
14
+
t.eq(out.tags, "json,yaml,etc")
15
+
t.eq(out.options, "")
16
+
end
17
+
18
+
st["should parse tags separated by command and spaces"] = function()
19
+
local out = require("gopher.struct_tags").parse_args {
20
+
"json,yaml",
21
+
"json=omitempty",
22
+
"xml=something",
23
+
}
24
+
25
+
t.eq(out.tags, "json,yaml,xml")
26
+
t.eq(out.options, "json=omitempty,xml=something")
27
+
end
28
+
29
+
st["should parse tag with an option"] = function()
30
+
local out = require("gopher.struct_tags").parse_args {
31
+
"json=omitempty",
32
+
"xml",
33
+
"xml=theoption",
34
+
}
35
+
36
+
t.eq(out.tags, "json,xml")
37
+
t.eq(out.options, "json=omitempty,xml=theoption")
38
+
end
39
+
40
+
st["should parse tags with an option"] = function()
41
+
local out = require("gopher.struct_tags").parse_args { "json=omitempty", "yaml" }
42
+
43
+
t.eq(out.tags, "json,yaml")
44
+
t.eq(out.options, "json=omitempty")
45
+
end
46
+
47
+
st["should parse tags with an option separated with comma"] = function()
48
+
local out = require("gopher.struct_tags").parse_args { "json=omitempty,yaml" }
49
+
50
+
t.eq(out.tags, "json,yaml")
51
+
t.eq(out.options, "json=omitempty")
52
+
end
53
+
54
+
st["should parse tags with options specified separately"] = function()
55
+
local out = require("gopher.struct_tags").parse_args { "json", "yaml", "json=omitempty" }
56
+
57
+
t.eq(out.tags, "json,yaml")
58
+
t.eq(out.options, "json=omitempty")
59
+
end
60
+
61
+
st["should parse tags with options specified separately and separated by comma"] = function()
62
+
local out = require("gopher.struct_tags").parse_args { "json,yaml,json=omitempty" }
63
+
64
+
t.eq(out.tags, "json,yaml")
65
+
t.eq(out.options, "json=omitempty")
66
+
end
67
+
68
+
return T
+59
spec/unit/utils_test.lua
+59
spec/unit/utils_test.lua
···
1
+
local t = require "spec.testutils"
2
+
local _, T, utils = t.setup "utils"
3
+
4
+
utils["should .remove_empty_lines()"] = function()
5
+
local u = require "gopher._utils"
6
+
local inp = { "hi", "", "a", "", "", "asdf" }
7
+
8
+
t.eq(u.remove_empty_lines(inp), { "hi", "a", "asdf" })
9
+
end
10
+
11
+
utils["should .readfile_joined()"] = function()
12
+
local data = "line1\nline2\nline3"
13
+
local tmp = t.tmpfile()
14
+
local u = require "gopher._utils"
15
+
16
+
t.writefile(tmp, data)
17
+
t.eq(u.readfile_joined(tmp), data)
18
+
end
19
+
20
+
utils["should .trimend()"] = function()
21
+
local u = require "gopher._utils"
22
+
t.eq(u.trimend " hi ", " hi")
23
+
end
24
+
25
+
utils["should add .indent() spaces"] = function()
26
+
local u = require "gopher._utils"
27
+
local line = " func Test() error {"
28
+
local indent = 4
29
+
30
+
t.eq(" ", u.indent(line, indent))
31
+
end
32
+
33
+
utils["should add .indent() a tab"] = function()
34
+
local u = require "gopher._utils"
35
+
local line = "\tfunc Test() error {"
36
+
local indent = 1
37
+
38
+
t.eq("\t", u.indent(line, indent))
39
+
end
40
+
41
+
utils["should add .indent() 2 tabs"] = function()
42
+
local u = require "gopher._utils"
43
+
local line = "\t\tfunc Test() error {"
44
+
local indent = 2
45
+
46
+
t.eq("\t\t", u.indent(line, indent))
47
+
end
48
+
49
+
utils["should .list_unique on list with duplicates"] = function()
50
+
local u = require "gopher._utils"
51
+
t.eq({ "json", "xml" }, u.list_unique { "json", "xml", "json" })
52
+
end
53
+
54
+
utils["should .list_unique on list with no duplicates"] = function()
55
+
local u = require "gopher._utils"
56
+
t.eq({ "json", "xml" }, u.list_unique { "json", "xml" })
57
+
end
58
+
59
+
return T
+23
vhs/Taskfile.yml
+23
vhs/Taskfile.yml
···
1
+
version: "3"
2
+
tasks:
3
+
generate:
4
+
deps:
5
+
- comment
6
+
- iferr
7
+
- tags
8
+
- impl
9
+
10
+
comment:
11
+
cmd: vhs comment.tape
12
+
13
+
iferr:
14
+
cmd: vhs iferr.tape
15
+
16
+
tags:
17
+
cmd: vhs tags.tape
18
+
19
+
impl:
20
+
cmd: vhs impl.tape
21
+
22
+
json2go:
23
+
cmd: vhs json2go.tape
vhs/comment.gif
vhs/comment.gif
This is a binary file and will not be displayed.
+7
vhs/comment.go
+7
vhs/comment.go
+29
vhs/comment.tape
+29
vhs/comment.tape
···
1
+
Output comment.gif
2
+
Require nvim
3
+
4
+
Set FontFamily "JetBrains Mono"
5
+
Set Height 800
6
+
Set Width 1500
7
+
Set Padding 20
8
+
Set Shell "bash"
9
+
Set Theme "tokyonight"
10
+
Set TypingSpeed 250ms
11
+
12
+
Hide Type@0ms "./nvim.sh comment.go" Enter Show
13
+
14
+
# package
15
+
Type ":GoCmt" Enter Sleep 500ms Escape Sleep 700ms
16
+
17
+
# func
18
+
Type@400ms "jjj"
19
+
Type ":GoCmt" Enter Sleep 500ms Escape Sleep 700ms
20
+
21
+
# struct
22
+
Type@400ms "jjj"
23
+
Type ":GoCmt" Enter Sleep 500ms Escape Sleep 700ms
24
+
25
+
# interface
26
+
Type@400ms "jjj"
27
+
Type ":GoCmt" Enter Sleep 500ms Escape Sleep 700ms
28
+
29
+
Sleep 5s
vhs/iferr.gif
vhs/iferr.gif
This is a binary file and will not be displayed.
+11
vhs/iferr.go
+11
vhs/iferr.go
+18
vhs/iferr.tape
+18
vhs/iferr.tape
···
1
+
Output iferr.gif
2
+
Require nvim
3
+
Require iferr
4
+
5
+
Set FontFamily "JetBrains Mono"
6
+
Set Height 800
7
+
Set Width 1500
8
+
Set Padding 20
9
+
Set Shell "bash"
10
+
Set Theme "tokyonight"
11
+
Set TypingSpeed 250ms
12
+
13
+
Hide Type@0ms "./nvim.sh iferr.go" Enter Show
14
+
15
+
Type "3j"
16
+
Type ":GoIfErr" Sleep 500ms Enter
17
+
18
+
Sleep 5s
vhs/impl.gif
vhs/impl.gif
This is a binary file and will not be displayed.
+18
vhs/impl.tape
+18
vhs/impl.tape
···
1
+
Output impl.gif
2
+
Require nvim
3
+
Require impl
4
+
5
+
Set FontFamily "JetBrains Mono"
6
+
Set Height 800
7
+
Set Width 1500
8
+
Set Padding 20
9
+
Set Shell "bash"
10
+
Set Theme "tokyonight"
11
+
Set TypingSpeed 250ms
12
+
13
+
Hide Type@0ms "./nvim.sh impl.go" Enter Show
14
+
15
+
Type "2j"
16
+
Type ":GoImpl c io.Reader" Sleep 700ms Enter
17
+
18
+
Sleep 5s
vhs/json2go.gif
vhs/json2go.gif
This is a binary file and will not be displayed.
+27
vhs/json2go.tape
+27
vhs/json2go.tape
···
1
+
Output json2go.gif
2
+
Require nvim
3
+
Require json2go
4
+
5
+
Set FontFamily "JetBrains Mono"
6
+
Set Height 800
7
+
Set Width 1500
8
+
Set Padding 20
9
+
Set Shell "bash"
10
+
Set Theme "tokyonight"
11
+
Set TypingSpeed 250ms
12
+
13
+
Hide Type@0ms "./nvim.sh json2go.go" Enter Show
14
+
15
+
Type@0ms "G"
16
+
Type@400ms ":GoJson" Sleep 500ms Enter
17
+
Type@70ms "{" Enter
18
+
Type@70ms `"json": true,` Enter
19
+
Type@70ms `"user": {"name": "Name", "of_age": true}` Enter
20
+
Type@70ms "}"
21
+
Escape Type@500ms ":wq" Enter
22
+
Sleep 1s
23
+
24
+
Type@25ms "G2o" Escape
25
+
Type@120ms `:GoJson {"json": true}` Enter
26
+
27
+
Sleep 5s