+1
.envrc
+1
.envrc
+3
-1
.github/workflows/linters.yml
+3
-1
.github/workflows/linters.yml
+11
-9
.github/workflows/tests.yml
+11
-9
.github/workflows/tests.yml
···
14
14
os: [ubuntu-latest]
15
15
version:
16
16
- stable
17
-
# - nightly # TODO: enable when 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
18
25
runs-on: ${{ matrix.os }}
19
26
steps:
20
27
- name: Install Task
···
35
42
neovim: true
36
43
version: ${{ matrix.version }}
37
44
38
-
- uses: actions/checkout@v3
45
+
- uses: actions/checkout@v4
39
46
40
47
- name: Cache .tests
41
48
uses: actions/cache@v4
···
47
54
key: ${{ runner.os }}-tests-${{ hashFiles('${{ github.workspace }}/.tests') }}
48
55
49
56
- name: Install Go bins
50
-
run: |
51
-
# TODO: install with :GoInstallDeps
52
-
go install github.com/fatih/gomodifytags@latest
53
-
go install github.com/josharian/impl@latest
54
-
go install github.com/cweill/gotests/...@latest
55
-
go install github.com/koron/iferr@latest
57
+
run: task install-deps
56
58
57
59
- name: Run Tests
58
60
run: |
59
61
nvim --version
60
-
task tests
62
+
task test
-10
.luarc.json
-10
.luarc.json
+10
-12
CONTRIBUTING.md
+10
-12
CONTRIBUTING.md
···
1
1
# Contributing to `gopher.nvim`
2
-
3
2
Thank you for taking the time to submit some code to gopher.nvim. It means a lot!
4
3
5
4
### Task running
6
-
7
5
In this codebase for running tasks is used [Taskfile](https://taskfile.dev).
8
6
You can install it with:
9
7
```bash
10
8
go install github.com/go-task/task/v3/cmd/task@latest
11
9
```
12
10
13
-
### Styling and formatting
14
-
11
+
### Formatting and linting
15
12
Code is formatted by [stylua](https://github.com/JohnnyMorganz/StyLua) and linted using [selene](https://github.com/Kampfkarren/selene).
16
13
You can install these with:
17
14
···
22
19
23
20
For formatting use this following commands, or setup your editor to integrate with selene/stylua:
24
21
```bash
25
-
task stylua
26
-
task lint # lintering and format chewing
22
+
task format
23
+
task lint
27
24
```
28
25
29
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.
30
29
31
-
Here we are using [mini.doc](https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-doc.md)
32
-
for generating help files based on EmmyLua-like annotations in comments
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.
33
32
34
33
You can generate docs with:
35
34
```bash
36
-
task docgen
35
+
task docgen # generates vimhelp
36
+
task vhs:generate # generates demo gifs
37
37
```
38
38
39
39
### Commit messages
40
-
41
40
We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), please follow it.
42
41
43
42
### Testing
44
-
45
43
For testing this plugins uses [mini.test](https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-test.md).
46
44
All tests live in [/spec](./spec) dir.
47
45
48
46
You can run tests with:
49
47
```bash
50
-
task tests
48
+
task test
51
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.
+122
-48
README.md
+122
-48
README.md
···
4
4
5
5
Minimalistic plugin for Go development in Neovim written in Lua.
6
6
7
-
It's **NOT** an LSP tool, the main goal of this plugin is to 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
8
9
-
> If you want to use new and maybe undocumented, and unstable features you might use [develop](https://github.com/olexsmir/gopher.nvim/tree/develop) branch.
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)
10
17
11
18
## Install (using [lazy.nvim](https://github.com/folke/lazy.nvim))
12
19
13
20
Requirements:
14
21
15
22
- **Neovim 0.10** or later
16
-
- Treesitter `go` parser(`:TSInstall go`)
17
-
- [Go](https://github.com/golang/go) installed (tested on 1.23)
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)
18
30
19
31
```lua
32
+
-- NOTE: this plugin is already lazy-loaded and adds only about 1ms
33
+
-- of load time to your config
20
34
{
21
35
"olexsmir/gopher.nvim",
22
36
ft = "go",
23
37
-- branch = "develop"
24
-
dependencies = {
25
-
"nvim-treesitter/nvim-treesitter",
26
-
},
27
-
-- (optional) will update plugin's deps on every update
38
+
-- (optional) updates the plugin's dependencies on each update
28
39
build = function()
29
40
vim.cmd.GoInstallDeps()
30
41
end,
42
+
---@module "gopher"
31
43
---@type gopher.Config
32
44
opts = {},
33
45
}
34
46
```
35
47
36
-
## Configuration
37
-
38
-
> [!IMPORTANT]
39
-
>
40
-
> If you need more info look `:h gopher.nvim`
41
-
42
-
**Take a look at default options**
43
-
44
-
```lua
45
-
require("gopher").setup {
46
-
commands = {
47
-
go = "go",
48
-
gomodifytags = "gomodifytags",
49
-
gotests = "gotests",
50
-
impl = "impl",
51
-
iferr = "iferr",
52
-
dlv = "dlv",
53
-
},
54
-
gotests = {
55
-
-- gotests doesn't have template named "default" so this plugin uses "default" to set the default template
56
-
template = "default",
57
-
-- path to a directory containing custom test code templates
58
-
template_dir = nil,
59
-
-- switch table tests from using slice to map (with test name for the key)
60
-
named = false,
61
-
},
62
-
gotag = {
63
-
transform = "snakecase",
64
-
},
65
-
}
66
-
```
67
-
68
48
## Features
69
-
70
-
<!-- markdownlint-disable -->
71
49
72
50
<details>
73
51
<summary>
···
84
62
- [impl](https://github.com/josharian/impl)
85
63
- [gotests](https://github.com/cweill/gotests)
86
64
- [iferr](https://github.com/koron/iferr)
87
-
- [dlv](github.com/go-delve/delve/cmd/dlv)
65
+
- [json2go](https://github.com/olexsmir/json2go)
88
66
</details>
89
67
90
68
<details>
···
92
70
<b>Add and remove tags for structs via <a href="https://github.com/fatih/gomodifytags">gomodifytags</a></b>
93
71
</summary>
94
72
73
+

74
+
95
75
By default `json` tag will be added/removed, if not set:
96
76
97
77
```vim
98
78
" add json tag
99
79
:GoTagAdd json
80
+
81
+
" add json tag with omitempty option
82
+
:GoTagAdd json=omitempty
100
83
101
84
" remove yaml tag
102
85
:GoTagRm yaml
···
169
152
<b>Interface implementation via <a href="https://github.com/josharian/impl">impl<a></b>
170
153
</summary>
171
154
155
+

156
+
172
157
Syntax of the command:
173
158
```vim
174
159
:GoImpl [receiver] [interface]
···
192
177
<b>Generate boilerplate for doc comments</b>
193
178
</summary>
194
179
180
+

181
+
195
182
First set a cursor on **public** package/function/interface/struct and execute:
196
183
197
184
```vim
···
199
186
```
200
187
</details>
201
188
189
+
<details>
190
+
<summary>
191
+
<b>Convert json to Go types</b>
192
+
</summary>
193
+
194
+

195
+
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.
198
+
199
+
Alternatively, you can pass JSON directly as an argument:
200
+
```vim
201
+
:GoJson {"name": "Alice", "age": 30}
202
+
```
203
+
204
+
Additionally, `gopher.json2go` provides lua api, see `:h gopher.nvim-json2go` for details.
205
+
</details>
206
+
202
207
203
208
<details>
204
209
<summary>
205
210
<b>Generate <code>if err != nil {</code> via <a href="https://github.com/koron/iferr">iferr</a></b>
206
211
</summary>
207
212
213
+

214
+
208
215
Set the cursor on the line with `err` and execute
209
216
210
217
```vim
···
212
219
```
213
220
</details>
214
221
215
-
## Contributing
222
+
## Configuration
223
+
224
+
> [!IMPORTANT]
225
+
>
226
+
> If you need more info look `:h gopher.nvim`
227
+
228
+
**Take a look at default options (might be a bit outdated, look `:h gopher.nvim-config`)**
229
+
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,
234
+
235
+
-- timeout for running internal commands
236
+
timeout = 2000,
237
+
238
+
-- timeout for running installer commands(e.g :GoDepsInstall, :GoDepsInstallSync)
239
+
installer_timeout = 999999,
240
+
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",
253
+
254
+
-- path to a directory containing custom test code templates
255
+
template_dir = nil,
256
+
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",
216
262
217
-
PRs are always welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md)
263
+
-- default tags to add to struct fields
264
+
default_tag = "json",
218
265
219
-
## Thanks
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",
220
279
221
-
- [go.nvim](https://github.com/ray-x/go.nvim)
222
-
- [iferr](https://github.com/koron/iferr)
280
+
-- name of autogenerated struct
281
+
-- e.g: "MySuperCoolName"
282
+
type_name = nil,
283
+
},
284
+
}
285
+
```
286
+
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`.
291
+
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)
+24
-17
Taskfile.yml
+24
-17
Taskfile.yml
···
1
1
version: "3"
2
+
3
+
includes:
4
+
vhs:
5
+
taskfile: ./vhs/Taskfile.yml
6
+
dir: ./vhs
7
+
2
8
tasks:
3
9
lint:
4
-
desc: runs all linters
5
10
cmds:
6
-
- task: selene
11
+
- selene .
7
12
- stylua --check .
8
13
9
-
selene:
10
-
desc: runs lua linter(selene)
11
-
cmds:
12
-
- selene .
14
+
format:
15
+
cmd: stylua .
13
16
14
-
stylua:
15
-
desc: runs lua formatter
16
-
cmds:
17
-
- stylua .
18
-
19
-
tests:
20
-
desc: run all tests
17
+
test:
21
18
cmds:
22
19
- |
23
-
nvim --headless \
20
+
nvim --clean --headless \
24
21
-u ./scripts/minimal_init.lua \
25
-
-c "lua MiniTest.run()"
22
+
-c "lua MiniTest.run()" \
23
+
-c ":qa!"
24
+
nvim:
25
+
cmd: nvim --clean -u "./scripts/minimal_init.lua" {{ .CLI_ARGS }}
26
26
27
27
docgen:
28
28
desc: generate vimhelp
29
29
cmds:
30
30
- |
31
-
nvim --noplugin \
32
-
--headless \
31
+
nvim --clean --headless \
33
32
-u "./scripts/minimal_init.lua" \
34
33
-c "luafile ./scripts/docgen.lua" \
35
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
+138
-79
doc/gopher.nvim.txt
+138
-79
doc/gopher.nvim.txt
···
1
-
*gopher.nvim*
1
+
*gopher.nvim* Enhance your golang experience
2
+
3
+
MIT License Copyright (c) 2025 Oleksandr Smirnov
2
4
3
5
==============================================================================
4
6
5
7
gopher.nvim is a minimalistic plugin for Go development in Neovim written in Lua.
6
8
It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim.
7
9
8
-
------------------------------------------------------------------------------
9
-
*gopher.nvim-table-of-contents*
10
10
Table of Contents
11
-
Setup....................................................|gopher.nvim-setup|
12
-
Install dependencies..............................|gopher.nvim-install-deps|
13
-
Configuration...........................................|gopher.nvim-config|
14
-
Modify struct tags.................................|gopher.nvim-struct-tags|
15
-
Auto implementation of interface methods..................|gopher.nvim-impl|
16
-
Generating unit tests boilerplate......................|gopher.nvim-gotests|
17
-
Iferr....................................................|gopher.nvim-iferr|
18
-
Generate comments.....................................|gopher.nvim-comments|
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|
19
21
20
22
------------------------------------------------------------------------------
21
-
*gopher.nvim-setup*
23
+
*gopher.nvim-setup()*
22
24
`gopher.setup`({user_config})
23
25
Setup function. This method simply merges default config with opts table.
24
26
You can read more about configuration at |gopher.nvim-config|
25
27
Calling this function is optional, if you ok with default settings.
26
-
See |gopher.nvim.config-defaults|
28
+
See |gopher.nvim.config|
27
29
28
30
Usage ~
29
-
`require("gopher").setup {}` (replace `{}` with your `config` table)
31
+
>lua
32
+
require("gopher").setup {} -- use default config or replace {} with your own
33
+
<
30
34
Parameters ~
31
-
{user_config} `(gopher.Config)`
35
+
{user_config} `(gopher.Config)` See |gopher.nvim-config|
32
36
33
37
------------------------------------------------------------------------------
34
-
*gopher.nvim-install-deps*
38
+
*gopher.nvim-dependencies*
35
39
`gopher.install_deps`
36
-
Gopher.nvim implements most of its features using third-party tools.
37
-
To install these tools, you can run `:GoInstallDeps` command
38
-
or call `require("gopher").install_deps()` if you want to use lua api.
39
-
By default dependencies will be installed asynchronously, to install them synchronously pass `{sync = true}` as an argument.
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.
40
45
41
46
42
47
==============================================================================
43
48
------------------------------------------------------------------------------
44
49
*gopher.nvim-config*
45
-
config it is the place where you can configure the plugin.
46
-
also this is optional is you're ok with default settings.
47
-
You can look at default options |gopher.nvim-config-defaults|
48
-
49
-
------------------------------------------------------------------------------
50
-
*gopher.nvim-config-defaults*
51
50
`default_config`
52
51
>lua
53
52
local default_config = {
54
-
--minidoc_replace_end
55
-
56
53
-- log level, you might consider using DEBUG or TRACE for debugging the plugin
57
54
---@type number
58
55
log_level = vim.log.levels.INFO,
59
56
60
-
-- timeout for running commands
57
+
-- timeout for running internal commands
61
58
---@type number
62
59
timeout = 2000,
63
60
64
-
--- whether to setup plugin commands or not
65
-
---@type boolean
66
-
setup_commands = true,
61
+
-- timeout for running installer commands(e.g :GoDepsInstall, :GoDepsInstallSync)
62
+
---@type number
63
+
installer_timeout = 999999,
67
64
68
65
-- user specified paths to binaries
69
66
---@class gopher.ConfigCommand
···
73
70
gotests = "gotests",
74
71
impl = "impl",
75
72
iferr = "iferr",
76
-
dlv = "dlv",
73
+
json2go = "json2go",
77
74
},
78
75
---@class gopher.ConfigGotests
79
76
gotests = {
80
-
-- gotests doesn't have template named "default" so this plugin uses "default" to set the default template
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.
81
79
template = "default",
80
+
82
81
-- path to a directory containing custom test code templates
83
82
---@type string|nil
84
83
template_dir = nil,
85
-
-- switch table tests from using slice to map (with test name for the key)
84
+
85
+
-- use named tests(map with test name as key) in table tests(slice of structs by default)
86
86
named = false,
87
87
},
88
88
---@class gopher.ConfigGoTag
···
92
92
93
93
-- default tags to add to struct fields
94
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,
95
100
},
101
+
---@class gopher.ConfigIfErr
96
102
iferr = {
97
-
-- choose a custom error message
103
+
-- choose a custom error message, nil to use default
104
+
-- e.g: `message = 'fmt.Errorf("failed to %w", err)'`
98
105
---@type string|nil
99
106
message = nil,
100
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
+
},
101
119
}
102
120
<
103
121
Class ~
104
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.
105
133
106
134
107
135
==============================================================================
108
136
------------------------------------------------------------------------------
109
137
*gopher.nvim-struct-tags*
110
-
struct-tags is utilizing the `gomodifytags` tool to add or remove tags to struct fields.
111
-
Usage ~
138
+
139
+
`struct_tags` is utilizing the `gomodifytags` tool to add or remove tags to
140
+
struct fields.
112
141
113
-
How to add/remove tags to struct fields:
142
+
Usage ~
114
143
115
-
------------------------------------------------------------------------------
144
+
How to add/remove/clear tags to struct fields:
145
+
1. Place cursor on the struct
116
146
2. Run `:GoTagAdd json` to add json tags to struct fields
117
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
+
118
154
119
155
NOTE: if you dont specify the tag it will use `json` as default
120
156
···
134
170
Name string `yaml:name`
135
171
}
136
172
<
173
+
174
+
==============================================================================
137
175
------------------------------------------------------------------------------
138
-
*struct_tags.add()*
139
-
`struct_tags.add`({...})
140
-
tags to a struct under the cursor
141
-
Parameters ~
142
-
{...} `(string)` Tags to add to the struct fields. If not provided, it will use [config.gotag.default_tag]
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.
143
185
186
+
Alternatively, you can pass JSON directly as an argument:
187
+
>vim
188
+
:GoJson {"name": "Alice", "age": 30}
189
+
<
144
190
------------------------------------------------------------------------------
145
-
*struct_tags.remove()*
146
-
`struct_tags.remove`({...})
147
-
tags from a struct under the cursor
191
+
*json2go.transform()*
192
+
`json2go.transform`({json_str})
193
+
148
194
Parameters ~
149
-
{...} `(string)` Tags to add to the struct fields. If not provided, it will use [config.gotag.default_tag]
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.
150
198
151
199
------------------------------------------------------------------------------
152
-
*struct_tags.clear()*
153
-
`struct_tags.clear`()
154
-
all tags from a struct under the cursor
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)`
155
208
156
209
157
210
==============================================================================
158
211
------------------------------------------------------------------------------
159
212
*gopher.nvim-impl*
160
-
impl is utilizing the `impl` tool to generate method stubs for interfaces.
213
+
214
+
Integration of `impl` tool to generate method stubs for interfaces.
215
+
161
216
Usage ~
162
217
163
218
1. Automatically implement an interface for a struct:
164
-
- Place your cursor on the struct where you want to implement the interface.
165
-
- Run `:GoImpl io.Reader`
166
-
- This will automatically determine the receiver and implement the `io.Reader` interface.
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.
167
222
168
223
2. Specify a custom receiver:
169
-
- Place your cursor on the struct
170
-
- Run `:GoImpl w io.Writer`, where:
171
-
- `w` is the receiver.
172
-
- `io.Writer` is the interface to implement.
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.
173
228
174
229
3. Explicitly specify the receiver, struct, and interface:
175
-
- No need to place the cursor on the struct if all arguments are provided.
176
-
- Run `:GoImpl r RequestReader io.Reader`, where:
177
-
- `r` is the receiver.
178
-
- `RequestReader` is the struct.
179
-
- `io.Reader` is the interface to implement.
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.
180
235
181
236
Example:
182
237
>go
···
197
252
Usage ~
198
253
199
254
- Generate unit test for specific function/method:
200
-
1. Place your cursor on the desired function/method.
201
-
2. Run `:GoTestAdd`
255
+
1. Place your cursor on the desired function/method.
256
+
2. Run `:GoTestAdd`
202
257
203
258
- Generate unit tests for *all* functions/methods in current file:
204
-
- run `:GoTestsAll`
259
+
- run `:GoTestsAll`
205
260
206
261
- Generate unit tests *only* for *exported(public)* functions/methods:
207
-
- run `:GoTestsExp`
208
-
209
-
You can also specify the template to use for generating the tests. See |gopher.nvim-config|
210
-
More details about templates can be found at: https://github.com/cweill/gotests
211
-
212
-
------------------------------------------------------------------------------
213
-
*gopher.nvim-gotests-named*
262
+
- run `:GoTestsExp`
214
263
215
-
You can enable named tests in the config if you prefer using named tests.
264
+
You can also specify the template to use for generating the tests.
216
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|.
217
269
218
270
219
271
==============================================================================
220
272
------------------------------------------------------------------------------
221
273
*gopher.nvim-iferr*
222
-
If you're using `iferr` tool, this module provides a way to automatically insert `if err != nil` check.
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
+
223
278
Usage ~
224
279
Execute `:GoIfErr` near any `err` variable to insert the check
225
280
···
227
282
==============================================================================
228
283
------------------------------------------------------------------------------
229
284
*gopher.nvim-comments*
285
+
286
+
This module provides a way to generate comments for Go code.
287
+
230
288
Usage ~
231
-
Execute `:GoCmt` to generate a comment for the current function/method/struct/etc on this line.
232
-
This module provides a way to generate comments for Go code.
289
+
290
+
Set cursor on line with function/method/struct/etc and
291
+
run `:GoCmt` to generate a comment.
233
292
234
293
235
294
vim:tw=78:ts=8:noet:ft=help:norl:
+40
-13
lua/gopher/_utils/init.lua
+40
-13
lua/gopher/_utils/init.lua
···
3
3
local utils = {}
4
4
5
5
---@param msg string
6
-
---@param lvl? number
7
-
function utils.deferred_notify(msg, lvl)
8
-
lvl = lvl or vim.log.levels.INFO
9
-
vim.defer_fn(function()
10
-
vim.notify(msg, lvl, {
11
-
title = c.___plugin_name,
12
-
})
13
-
log.debug(msg)
14
-
end, 0)
15
-
end
16
-
17
-
---@param msg string
18
-
---@param lvl? number
6
+
---@param lvl? integer by default `vim.log.levels.INFO`
19
7
function utils.notify(msg, lvl)
20
8
lvl = lvl or vim.log.levels.INFO
21
9
vim.notify(msg, lvl, {
10
+
---@diagnostic disable-next-line:undefined-field
22
11
title = c.___plugin_name,
23
12
})
24
13
log.debug(msg)
···
40
29
end
41
30
end
42
31
return res
32
+
end
33
+
34
+
---@param s string
35
+
---@return string
36
+
function utils.trimend(s)
37
+
local r, _ = string.gsub(s, "%s+$", "")
38
+
return r
39
+
end
40
+
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
52
+
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
60
+
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
67
+
end
68
+
end
69
+
return tbl
43
70
end
44
71
45
72
return utils
+3
-2
lua/gopher/_utils/log.lua
+3
-2
lua/gopher/_utils/log.lua
···
3
3
-- for the code i have stolen(or have inspected by idk)
4
4
local c = require "gopher.config"
5
5
6
-
---@class Gopher.Logger
6
+
---@class gopher.Logger
7
7
---@field get_outfile fun():string
8
8
---@field trace fun(...)
9
9
---@field fmt_trace fun(...)
···
18
18
19
19
local config = {
20
20
-- Name of the plugin. Prepended to log messages
21
+
---@diagnostic disable-next-line:undefined-field
21
22
name = c.___plugin_name,
22
23
23
24
-- Should print the output to neovim while running
···
43
44
float_precision = 0.01,
44
45
}
45
46
46
-
---@type Gopher.Logger
47
+
---@type gopher.Logger
47
48
---@diagnostic disable-next-line: missing-fields
48
49
local log = {}
49
50
-51
lua/gopher/_utils/runner/gocmd.lua
-51
lua/gopher/_utils/runner/gocmd.lua
···
1
-
local r = require "gopher._utils.runner"
2
-
local c = require("gopher.config").commands
3
-
local u = require "gopher._utils"
4
-
local gocmd = {}
5
-
6
-
---@param args string[]
7
-
---@return string[]
8
-
local function if_get(args)
9
-
for i, arg in ipairs(args) do
10
-
local m = string.match(arg, "^https://(.*)$") or string.match(arg, "^http://(.*)$") or arg
11
-
table.remove(args, i)
12
-
table.insert(args, i, m)
13
-
end
14
-
return args
15
-
end
16
-
17
-
---@param args unknown[]
18
-
---@return string[]
19
-
local function if_generate(args)
20
-
if #args == 1 and args[1] == "%" then
21
-
args[1] = vim.fn.expand "%"
22
-
end
23
-
return args
24
-
end
25
-
26
-
---@param subcmd string
27
-
---@param args string[]
28
-
---@return string
29
-
function gocmd.run(subcmd, args)
30
-
if #args == 0 and subcmd ~= "generate" then
31
-
error "please provide any arguments"
32
-
end
33
-
34
-
if subcmd == "get" then
35
-
args = if_get(args)
36
-
end
37
-
38
-
if subcmd == "generate" then
39
-
args = if_generate(args)
40
-
end
41
-
42
-
local rs = r.sync { c.go, subcmd, unpack(args) }
43
-
if rs.code ~= 0 then
44
-
error("go " .. subcmd .. " failed: " .. rs.stderr)
45
-
end
46
-
47
-
u.notify(c.go .. " " .. subcmd .. " ran successful")
48
-
return rs.stdout
49
-
end
50
-
51
-
return gocmd
-39
lua/gopher/_utils/runner/init.lua
-39
lua/gopher/_utils/runner/init.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
+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
+72
-27
lua/gopher/_utils/ts.lua
+72
-27
lua/gopher/_utils/ts.lua
···
1
1
local ts = {}
2
2
local queries = {
3
3
struct = [[
4
-
(type_spec name: (type_identifier) @_name
5
-
type: (struct_type))
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)
6
16
]],
7
17
func = [[
8
18
[(function_declaration name: (identifier) @_name)
9
-
(method_declaration name: (field_identifier) @_name)]
19
+
(method_declaration name: (field_identifier) @_name)
20
+
(method_elem name: (field_identifier) @_name)]
10
21
]],
11
22
package = [[
12
23
(package_identifier) @_name
···
16
27
name: (type_identifier) @_name
17
28
type: (interface_type))
18
29
]],
30
+
var = [[
31
+
[(var_declaration (var_spec name: (identifier) @_name))
32
+
(short_var_declaration
33
+
left: (expression_list (identifier) @_name @_var))]
34
+
]],
19
35
}
20
36
21
37
---@param parent_type string[]
22
38
---@param node TSNode
23
39
---@return TSNode?
24
-
local function get_parrent_node(parent_type, node)
40
+
local function get_parent_node(parent_type, node)
25
41
---@type TSNode?
26
42
local current = node
27
43
while current do
···
40
56
---@param query vim.treesitter.Query
41
57
---@param node TSNode
42
58
---@param bufnr integer
43
-
---@return {name:string}
59
+
---@return {name:string, is_varstruct:boolean}
44
60
local function get_captures(query, node, bufnr)
45
61
local res = {}
46
-
for _, match, _ in query:iter_matches(node, bufnr) do
47
-
for capture_id, captured_node in pairs(match) do
48
-
local capture_name = query.captures[capture_id]
49
-
if capture_name == "_name" then
50
-
res["name"] = vim.treesitter.get_node_text(captured_node, bufnr)
51
-
end
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
52
69
end
53
70
end
54
71
···
56
73
end
57
74
58
75
---@class gopher.TsResult
59
-
---@field name string
60
-
---@field start_line integer
61
-
---@field end_line integer
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{}{}`
62
81
63
82
---@param bufnr integer
64
83
---@param parent_type string[]
65
84
---@param query string
66
85
---@return gopher.TsResult
67
86
local function do_stuff(bufnr, parent_type, query)
68
-
local node = vim.treesitter.get_node {
69
-
bufnr = bufnr,
70
-
}
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 }
71
92
if not node then
72
-
error "No nodes found under cursor"
93
+
error "No nodes found under the cursor"
73
94
end
74
95
75
-
local parent_node = get_parrent_node(parent_type, node)
96
+
local parent_node = get_parent_node(parent_type, node)
76
97
if not parent_node then
77
-
error "No parent node found under cursor"
98
+
error "No parent node found under the cursor"
78
99
end
79
100
80
101
local q = vim.treesitter.query.parse("go", query)
81
102
local res = get_captures(q, parent_node, bufnr)
82
103
assert(res.name ~= nil, "No capture name found")
83
104
84
-
local start_row, _, end_row, _ = parent_node:range()
85
-
res["start_line"] = start_row + 1
86
-
res["end_line"] = end_row + 1
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
87
109
88
110
return res
89
111
end
···
91
113
---@param bufnr integer
92
114
function ts.get_struct_under_cursor(bufnr)
93
115
--- should be both type_spec and type_declaration
94
-
--- because in cases like `type ( T struct{}, U strict{} )`
95
-
--- i will be choosing always last struct in the list
96
-
return do_stuff(bufnr, { "type_spec", "type_declaration" }, queries.struct)
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)
97
133
end
98
134
99
135
---@param bufnr integer
100
136
function ts.get_func_under_cursor(bufnr)
101
137
--- since this handles both and funcs and methods we should check for both parent nodes
102
-
return do_stuff(bufnr, { "function_declaration", "method_declaration" }, queries.func)
138
+
return do_stuff(bufnr, {
139
+
"method_elem",
140
+
"function_declaration",
141
+
"method_declaration",
142
+
}, queries.func)
103
143
end
104
144
105
145
---@param bufnr integer
···
110
150
---@param bufnr integer
111
151
function ts.get_interface_under_cursor(bufnr)
112
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)
113
158
end
114
159
115
160
return ts
+35
-19
lua/gopher/comment.lua
+35
-19
lua/gopher/comment.lua
···
1
1
---@toc_entry Generate comments
2
2
---@tag gopher.nvim-comments
3
-
---@usage Execute `:GoCmt` to generate a comment for the current function/method/struct/etc on this line.
4
-
---@text This module provides a way to generate comments for Go code.
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.
5
9
6
10
local ts = require "gopher._utils.ts"
7
11
local log = require "gopher._utils.log"
12
+
local u = require "gopher._utils"
8
13
local comment = {}
9
14
10
-
---@param name string
11
-
---@return string
12
-
---@private
13
-
local function template(name)
14
-
return "// " .. name .. " "
15
-
end
16
-
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
+
---
17
18
---@param bufnr integer
19
+
---@param line string
18
20
---@return string
19
-
---@private
20
-
local function generate(bufnr)
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 .. " "
26
+
end
27
+
21
28
local s_ok, s_res = pcall(ts.get_struct_under_cursor, bufnr)
22
29
if s_ok then
23
-
return template(s_res.name)
30
+
return u.indent(line, s_res.indent) .. "// " .. s_res.name .. " "
31
+
end
32
+
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 .. " "
24
36
end
25
37
26
38
local f_ok, f_res = pcall(ts.get_func_under_cursor, bufnr)
27
39
if f_ok then
28
-
return template(f_res.name)
40
+
return u.indent(line, f_res.indent) .. "// " .. f_res.name .. " "
29
41
end
30
42
31
43
local i_ok, i_res = pcall(ts.get_interface_under_cursor, bufnr)
32
44
if i_ok then
33
-
return template(i_res.name)
45
+
return u.indent(line, i_res.indent) .. "// " .. i_res.name .. " "
34
46
end
35
47
36
48
local p_ok, p_res = pcall(ts.get_package_under_cursor, bufnr)
···
43
55
44
56
function comment.comment()
45
57
local bufnr = vim.api.nvim_get_current_buf()
46
-
local cmt = generate(bufnr)
47
-
log.debug("generated comment: " .. cmt)
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,
64
+
})
48
65
49
-
local pos = vim.fn.getcurpos()[2]
50
-
vim.fn.append(pos - 1, cmt)
51
-
vim.fn.setpos(".", { 0, pos, #cmt })
66
+
vim.fn.append(lnum - 1, cmt)
67
+
vim.fn.setpos(".", { bufnr, lnum, #cmt })
52
68
vim.cmd "startinsert!"
53
69
end
54
70
+63
-32
lua/gopher/config.lua
+63
-32
lua/gopher/config.lua
···
1
-
---@toc_entry Configuration
2
-
---@tag gopher.nvim-config
3
-
---@text config it is the place where you can configure the plugin.
4
-
--- also this is optional is you're ok with default settings.
5
-
--- You can look at default options |gopher.nvim-config-defaults|
6
-
7
1
---@type gopher.Config
8
-
---@private
2
+
---@dochide
3
+
---@diagnostic disable-next-line: missing-fields .setup() gets injected later
9
4
local config = {}
10
5
11
6
---@tag gopher.nvim-config.ConfigGoTagTransform
12
7
---@text Possible values for |gopher.Config|.gotag.transform:
13
8
---
14
-
---@private
9
+
---@dochide
15
10
---@alias gopher.ConfigGoTagTransform
16
11
---| "snakecase" "GopherUser" -> "gopher_user"
17
12
---| "camelcase" "GopherUser" -> "gopherUser"
···
20
15
---| "titlecase" "GopherUser" -> "Gopher User"
21
16
---| "keep" keeps the original field name
22
17
23
-
--minidoc_replace_start {
24
-
25
-
---@tag gopher.nvim-config-defaults
18
+
---@toc_entry Config
19
+
---@tag gopher.nvim-config
26
20
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
27
-
---
28
21
---@class gopher.Config
22
+
---@field setup fun(user_config?: gopher.Config)
29
23
local default_config = {
30
-
--minidoc_replace_end
31
-
32
24
-- log level, you might consider using DEBUG or TRACE for debugging the plugin
33
25
---@type number
34
26
log_level = vim.log.levels.INFO,
35
27
36
-
-- timeout for running commands
28
+
-- timeout for running internal commands
37
29
---@type number
38
30
timeout = 2000,
39
31
40
-
--- whether to setup plugin commands or not
41
-
---@type boolean
42
-
setup_commands = true,
32
+
-- timeout for running installer commands(e.g :GoDepsInstall, :GoDepsInstallSync)
33
+
---@type number
34
+
installer_timeout = 999999,
43
35
44
36
-- user specified paths to binaries
45
37
---@class gopher.ConfigCommand
···
49
41
gotests = "gotests",
50
42
impl = "impl",
51
43
iferr = "iferr",
52
-
dlv = "dlv",
44
+
json2go = "json2go",
53
45
},
54
46
---@class gopher.ConfigGotests
55
47
gotests = {
56
-
-- gotests doesn't have template named "default" so this plugin uses "default" to set the default template
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.
57
50
template = "default",
51
+
58
52
-- path to a directory containing custom test code templates
59
53
---@type string|nil
60
54
template_dir = nil,
61
-
-- switch table tests from using slice to map (with test name for the key)
55
+
56
+
-- use named tests(map with test name as key) in table tests(slice of structs by default)
62
57
named = false,
63
58
},
64
59
---@class gopher.ConfigGoTag
···
68
63
69
64
-- default tags to add to struct fields
70
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
71
},
72
+
---@class gopher.ConfigIfErr
72
73
iferr = {
73
-
-- choose a custom error message
74
+
-- choose a custom error message, nil to use default
75
+
-- e.g: `message = 'fmt.Errorf("failed to %w", err)'`
74
76
---@type string|nil
75
77
message = nil,
76
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,
89
+
},
77
90
}
78
91
--minidoc_afterlines_end
79
92
80
93
---@type gopher.Config
81
-
---@private
94
+
---@dochide
82
95
local _config = default_config
83
96
84
97
-- I am kinda secret so don't tell anyone about me even dont use me
···
89
102
_config.___plugin_name = "gopher.nvim" ---@diagnostic disable-line: inject-field
90
103
91
104
---@param user_config? gopher.Config
92
-
---@private
105
+
---@dochide
93
106
function config.setup(user_config)
94
-
_config = vim.tbl_deep_extend("force", default_config, user_config or {})
95
-
end
107
+
vim.validate("user_config", user_config, "table", true)
108
+
109
+
_config = vim.tbl_deep_extend("force", vim.deepcopy(default_config), user_config or {})
96
110
97
-
---@return boolean
98
-
---@private
99
-
function config.should_setup_commands()
100
-
return config.setup_commands
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" })
101
133
end
102
134
103
135
setmetatable(config, {
···
106
138
end,
107
139
})
108
140
109
-
---@return gopher.Config
110
-
---@private
141
+
---@dochide
111
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
+9
-12
lua/gopher/gotests.lua
+9
-12
lua/gopher/gotests.lua
···
3
3
---@text gotests is utilizing the `gotests` tool to generate unit tests boilerplate.
4
4
---@usage
5
5
--- - Generate unit test for specific function/method:
6
-
--- 1. Place your cursor on the desired function/method.
7
-
--- 2. Run `:GoTestAdd`
6
+
--- 1. Place your cursor on the desired function/method.
7
+
--- 2. Run `:GoTestAdd`
8
8
---
9
9
--- - Generate unit tests for *all* functions/methods in current file:
10
-
--- - run `:GoTestsAll`
10
+
--- - run `:GoTestsAll`
11
11
---
12
12
--- - Generate unit tests *only* for *exported(public)* functions/methods:
13
-
--- - run `:GoTestsExp`
13
+
--- - run `:GoTestsExp`
14
14
---
15
-
--- You can also specify the template to use for generating the tests. See |gopher.nvim-config|
16
-
--- More details about templates can be found at: https://github.com/cweill/gotests
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
17
18
---
18
-
19
-
---@tag gopher.nvim-gotests-named
20
-
---@text
21
-
--- You can enable named tests in the config if you prefer using named tests.
22
-
--- See |gopher.nvim-config|.
19
+
--- If you prefer named tests, you can enable them in |gopher.nvim-config|.
23
20
24
21
local c = require "gopher.config"
25
22
local ts_utils = require "gopher._utils.ts"
···
29
26
local gotests = {}
30
27
31
28
---@param args table
32
-
---@private
29
+
---@dochide
33
30
local function add_test(args)
34
31
if c.gotests.named then
35
32
table.insert(args, "-named")
+33
-56
lua/gopher/health.lua
+33
-56
lua/gopher/health.lua
···
1
+
local c = require("gopher.config").commands
1
2
local health = {}
2
-
local cmd = require("gopher.config").commands
3
3
4
4
local deps = {
5
-
plugin = {
6
-
{ lib = "nvim-treesitter", msg = "required for everything in gopher.nvim" },
7
-
},
5
+
vim_version = "nvim-0.10",
8
6
bin = {
9
7
{
10
-
bin = cmd.go,
11
-
msg = "required for `:GoGet`, `:GoMod`, `:GoGenerate`, `:GoWork`, `:GoInstallDeps`",
12
-
optional = false,
8
+
bin = c.go,
9
+
msg = "required for `:GoGet`, `:GoMod`, `:GoGenerate`, `:GoWork`, `:GoInstallDeps`, `:GoInstallDepsSync`",
13
10
},
14
-
{ bin = cmd.gomodifytags, msg = "required for `:GoTagAdd`, `:GoTagRm`", optional = true },
15
-
{ bin = cmd.impl, msg = "required for `:GoImpl`", optional = true },
16
-
{ bin = cmd.iferr, msg = "required for `:GoIfErr`", optional = true },
17
-
{
18
-
bin = cmd.gotests,
19
-
msg = "required for `:GoTestAdd`, `:GoTestsAll`, `:GoTestsExp`",
20
-
optional = true,
21
-
},
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`" },
22
15
},
23
16
treesitter = {
24
-
{ parser = "go", msg = "required for `gopher.nvim`" },
17
+
{ parser = "go", msg = "required for most of the parts of `gopher.nvim`" },
25
18
},
26
19
}
27
20
28
-
---@param module string
29
-
---@return boolean
30
-
local function is_lualib_found(module)
31
-
local is_found, _ = pcall(require, module)
32
-
return is_found
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
33
28
end
34
29
35
-
---@param bin string
36
-
---@return boolean
37
-
local function is_binary_found(bin)
38
-
return vim.fn.executable(bin) == 1
39
-
end
40
-
41
-
---@param ft string
42
-
---@return boolean
43
-
local function is_treesitter_parser_available(ft)
44
-
local ok, parser = pcall(vim.treesitter.get_parser, 0, ft)
45
-
return ok and parser ~= nil
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
46
38
end
47
39
48
40
function health.check()
49
-
vim.health.start "required plugins"
50
-
for _, plugin in ipairs(deps.plugin) do
51
-
if is_lualib_found(plugin.lib) then
52
-
vim.health.ok(plugin.lib .. " installed")
53
-
else
54
-
vim.health.error(plugin.lib .. " not found, " .. plugin.msg)
55
-
end
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")
56
46
end
57
47
58
-
vim.health.start "required binaries"
59
-
vim.health.info "all those binaries can be installed by `:GoInstallDeps`"
48
+
vim.health.start "Required binaries (those can be installed with `:GoInstallDeps`)"
60
49
for _, bin in ipairs(deps.bin) do
61
-
if is_binary_found(bin.bin) then
62
-
vim.health.ok(bin.bin .. " installed")
63
-
else
64
-
if bin.optional then
65
-
vim.health.warn(bin.bin .. " not found, " .. bin.msg)
66
-
else
67
-
vim.health.error(bin.bin .. " not found, " .. bin.msg)
68
-
end
69
-
end
50
+
check_binary(bin)
70
51
end
71
52
72
-
vim.health.start "required treesitter parsers"
53
+
vim.health.start "Treesitter"
73
54
for _, parser in ipairs(deps.treesitter) do
74
-
if is_treesitter_parser_available(parser.parser) then
75
-
vim.health.ok(parser.parser .. " parser installed")
76
-
else
77
-
vim.health.error(parser.parser .. " parser not found, " .. parser.msg)
78
-
end
55
+
check_treesitter(parser)
79
56
end
80
57
end
81
58
+7
-3
lua/gopher/iferr.lua
+7
-3
lua/gopher/iferr.lua
···
1
+
-- Thanks https://github.com/koron/iferr for vim implementation
2
+
1
3
---@toc_entry Iferr
2
4
---@tag gopher.nvim-iferr
3
-
---@text If you're using `iferr` tool, this module provides a way to automatically insert `if err != nil` check.
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
+
---
4
9
---@usage Execute `:GoIfErr` near any `err` variable to insert the check
5
10
6
11
local c = require "gopher.config"
···
9
14
local log = require "gopher._utils.log"
10
15
local iferr = {}
11
16
12
-
-- That's Lua implementation: https://github.com/koron/iferr
13
17
function iferr.iferr()
14
18
local curb = vim.fn.wordcount().cursor_bytes
15
19
local pos = vim.fn.getcurpos()[2]
···
38
42
39
43
vim.fn.append(pos, u.remove_empty_lines(vim.split(rs.stdout, "\n")))
40
44
vim.cmd [[silent normal! j=2j]]
41
-
vim.fn.setpos(".", pos)
45
+
vim.fn.setpos(".", pos --[[@as integer[] ]])
42
46
end
43
47
44
48
return iferr
+24
-15
lua/gopher/impl.lua
+24
-15
lua/gopher/impl.lua
···
1
1
---@toc_entry Auto implementation of interface methods
2
2
---@tag gopher.nvim-impl
3
-
---@text impl is utilizing the `impl` tool to generate method stubs for interfaces.
3
+
---@text
4
+
--- Integration of `impl` tool to generate method stubs for interfaces.
5
+
---
4
6
---@usage
5
7
--- 1. Automatically implement an interface for a struct:
6
-
--- - Place your cursor on the struct where you want to implement the interface.
7
-
--- - Run `:GoImpl io.Reader`
8
-
--- - This will automatically determine the receiver and implement the `io.Reader` interface.
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.
9
11
---
10
12
--- 2. Specify a custom receiver:
11
-
--- - Place your cursor on the struct
12
-
--- - Run `:GoImpl w io.Writer`, where:
13
-
--- - `w` is the receiver.
14
-
--- - `io.Writer` is the interface to implement.
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.
15
17
---
16
18
--- 3. Explicitly specify the receiver, struct, and interface:
17
-
--- - No need to place the cursor on the struct if all arguments are provided.
18
-
--- - Run `:GoImpl r RequestReader io.Reader`, where:
19
-
--- - `r` is the receiver.
20
-
--- - `RequestReader` is the struct.
21
-
--- - `io.Reader` is the interface to implement.
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.
22
24
---
23
25
--- Example:
24
26
--- >go
···
43
45
local iface, recv = "", ""
44
46
local bufnr = vim.api.nvim_get_current_buf()
45
47
46
-
if #args == 1 then -- :GoImpl io.Reader
48
+
if #args == 0 then
49
+
u.notify("arguments not provided. usage: :GoImpl f *File io.Reader", vim.log.levels.ERROR)
50
+
return
51
+
elseif #args == 1 then -- :GoImpl io.Reader
47
52
local st = ts_utils.get_struct_under_cursor(bufnr)
48
53
iface = args[1]
49
54
recv = string.lower(st.name) .. " *" .. st.name
···
56
61
iface = args[3]
57
62
end
58
63
59
-
local rs = r.sync { c.impl, "-dir", vim.fn.fnameescape(vim.fn.expand "%:p:h"), recv, iface }
64
+
assert(iface ~= "", "interface not provided")
65
+
assert(recv ~= "", "receiver not provided")
66
+
67
+
local dir = vim.fn.fnameescape(vim.fn.expand "%:p:h")
68
+
local rs = r.sync { c.impl, "-dir", dir, recv, iface }
60
69
if rs.code ~= 0 then
61
70
error("failed to implement interface: " .. rs.stderr)
62
71
end
+21
-28
lua/gopher/init.lua
+21
-28
lua/gopher/init.lua
···
1
-
--- *gopher.nvim*
1
+
--- *gopher.nvim* Enhance your golang experience
2
+
---
3
+
--- MIT License Copyright (c) 2025 Oleksandr Smirnov
2
4
---
3
5
--- ==============================================================================
4
6
---
5
7
--- gopher.nvim is a minimalistic plugin for Go development in Neovim written in Lua.
6
8
--- It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim.
7
-
9
+
---
8
10
--- Table of Contents
9
-
---@tag gopher.nvim-table-of-contents
10
11
---@toc
11
12
12
13
local log = require "gopher._utils.log"
13
14
local tags = require "gopher.struct_tags"
14
15
local tests = require "gopher.gotests"
15
-
local gocmd = require("gopher._utils.runner.gocmd").run
16
+
local go = require "gopher.go"
16
17
local gopher = {}
17
18
18
19
---@toc_entry Setup
19
-
---@tag gopher.nvim-setup
20
+
---@tag gopher.nvim-setup()
20
21
---@text Setup function. This method simply merges default config with opts table.
21
22
--- You can read more about configuration at |gopher.nvim-config|
22
23
--- Calling this function is optional, if you ok with default settings.
23
-
--- See |gopher.nvim.config-defaults|
24
+
--- See |gopher.nvim.config|
24
25
---
25
-
---@usage `require("gopher").setup {}` (replace `{}` with your `config` table)
26
-
---@param user_config gopher.Config
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|
27
30
gopher.setup = function(user_config)
28
31
log.debug "setting up config"
29
32
require("gopher.config").setup(user_config)
···
31
34
end
32
35
33
36
---@toc_entry Install dependencies
34
-
---@tag gopher.nvim-install-deps
35
-
---@text Gopher.nvim implements most of its features using third-party tools.
36
-
--- To install these tools, you can run `:GoInstallDeps` command
37
-
--- or call `require("gopher").install_deps()` if you want to use lua api.
38
-
--- By default dependencies will be installed asynchronously, to install them synchronously pass `{sync = true}` as an argument.
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.
39
43
gopher.install_deps = require("gopher.installer").install_deps
40
44
41
45
gopher.impl = require("gopher.impl").impl
···
54
58
all = tests.all_tests,
55
59
}
56
60
57
-
gopher.get = function(...)
58
-
gocmd("get", ...)
59
-
end
60
-
61
-
gopher.mod = function(...)
62
-
gocmd("mod", ...)
63
-
end
64
-
65
-
gopher.generate = function(...)
66
-
gocmd("generate", ...)
67
-
end
68
-
69
-
gopher.work = function(...)
70
-
gocmd("work", ...)
71
-
end
61
+
gopher.get = go.get
62
+
gopher.mod = go.mod
63
+
gopher.work = go.work
64
+
gopher.generate = go.generate
72
65
73
66
return gopher
+21
-7
lua/gopher/installer.lua
+21
-7
lua/gopher/installer.lua
···
1
-
local c = require("gopher.config").commands
1
+
local c = require "gopher.config"
2
2
local r = require "gopher._utils.runner"
3
3
local u = require "gopher._utils"
4
4
local log = require "gopher._utils.log"
···
9
9
impl = "github.com/josharian/impl@latest",
10
10
gotests = "github.com/cweill/gotests/...@develop",
11
11
iferr = "github.com/koron/iferr@latest",
12
+
json2go = "olexsmir.xyz/json2go/cmd/json2go@latest",
12
13
}
13
14
14
15
---@param opt vim.SystemCompleted
15
16
---@param url string
16
17
local function handle_intall_exit(opt, url)
17
18
if opt.code ~= 0 then
18
-
u.deferred_notify("go install failed: " .. url)
19
+
vim.schedule(function()
20
+
u.notify("go install failed: " .. url)
21
+
end)
22
+
19
23
log.error("go install failed:", "url", url, "opt", vim.inspect(opt))
20
24
return
21
25
end
22
26
23
-
u.deferred_notify("go install-ed: " .. url)
27
+
vim.schedule(function()
28
+
u.notify("go install-ed: " .. url)
29
+
end)
24
30
end
25
31
26
32
---@param url string
27
33
local function install(url)
28
-
r.async({ c.go, "install", url }, function(opt)
29
-
handle_intall_exit(opt, url)
34
+
vim.schedule(function()
35
+
u.notify("go install-ing: " .. url)
30
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 })
31
41
end
32
42
33
43
---@param url string
34
44
local function install_sync(url)
35
-
local rs = r.sync { c.go, "install", 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 })
36
50
handle_intall_exit(rs, url)
37
51
end
38
52
···
40
54
---@param opts? {sync:boolean}
41
55
function installer.install_deps(opts)
42
56
opts = opts or {}
43
-
for url, _ in pairs(urls) do
57
+
for _, url in pairs(urls) do
44
58
if opts.sync then
45
59
install_sync(url)
46
60
else
+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
-40
nvim.toml
-40
nvim.toml
···
3
3
4
4
[MiniTest]
5
5
any = true
6
-
7
-
[describe]
8
-
any = true
9
-
[[describe.args]]
10
-
type = "string"
11
-
[[describe.args]]
12
-
type = "function"
13
-
14
-
[it]
15
-
any = true
16
-
[[it.args]]
17
-
type = "string"
18
-
[[it.args]]
19
-
type = "function"
20
-
21
-
[before_each]
22
-
any = true
23
-
[[before_each.args]]
24
-
type = "function"
25
-
[[after_each.args]]
26
-
type = "function"
27
-
28
-
[assert]
29
-
any = true
30
-
31
-
[assert.is_not]
32
-
any = true
33
-
34
-
[[assert.equals.args]]
35
-
type = "any"
36
-
[[assert.equals.args]]
37
-
type = "any"
38
-
[[assert.equals.args]]
39
-
type = "any"
40
-
required = false
41
-
42
-
[[assert.same.args]]
43
-
type = "any"
44
-
[[assert.same.args]]
45
-
type = "any"
+10
pkg.json
+10
pkg.json
+91
-58
plugin/gopher.lua
+91
-58
plugin/gopher.lua
···
1
-
--- NOTE: runs in defer since this file before gopher.config
2
-
--- I'm not sure if this is the best to do this
3
-
vim.schedule(function()
4
-
if require("gopher.config").should_setup_commands() then
5
-
vim.api.nvim_create_user_command("GopherLog", function()
6
-
vim.cmd("tabnew " .. require("gopher._utils.log").get_outfile())
7
-
end, { nargs = 0 })
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.
8
6
9
-
vim.api.nvim_create_user_command("GoIfErr", function()
10
-
require("gopher").iferr()
11
-
end, { nargs = 0 })
7
+
if vim.g.gopher_register_commands == false then
8
+
return
9
+
end
12
10
13
-
vim.api.nvim_create_user_command("GoCmt", function()
14
-
require("gopher").comment()
15
-
end, { nargs = 0 })
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
16
22
17
-
vim.api.nvim_create_user_command("GoImpl", function(args)
18
-
require("gopher").impl(unpack(args.fargs))
19
-
end, { nargs = "*" })
23
+
cmd("GopherLog", function()
24
+
vim.cmd("tabnew " .. require("gopher._utils.log").get_outfile())
25
+
end)
20
26
21
-
-- :GoInstall
22
-
vim.api.nvim_create_user_command("GoInstallDeps", function()
23
-
require("gopher").install_deps()
24
-
end, { nargs = 0 })
27
+
cmd("GoIfErr", function()
28
+
require("gopher").iferr()
29
+
end)
25
30
26
-
vim.api.nvim_create_user_command("GoInstallDepsSync", function()
27
-
require("gopher").install_deps { sync = true }
28
-
end, { nargs = 0 })
31
+
cmd("GoCmt", function()
32
+
require("gopher").comment()
33
+
end)
29
34
30
-
--- :GoTag
31
-
vim.api.nvim_create_user_command("GoTagAdd", function(opts)
32
-
require("gopher").tags.add(unpack(opts.fargs))
33
-
end, { nargs = "*" })
35
+
cmd("GoImpl", function(args)
36
+
require("gopher").impl(unpack(args.fargs))
37
+
end, "*")
34
38
35
-
vim.api.nvim_create_user_command("GoTagRm", function(opts)
36
-
require("gopher").tags.rm(unpack(opts.fargs))
37
-
end, { nargs = "*" })
39
+
-- :GoInstall
40
+
cmd("GoInstallDeps", function()
41
+
require("gopher").install_deps()
42
+
end)
38
43
39
-
vim.api.nvim_create_user_command("GoTagClear", function()
40
-
require("gopher").tags.clear()
41
-
end, { nargs = 0 })
44
+
cmd("GoInstallDepsSync", function()
45
+
require("gopher").install_deps { sync = true }
46
+
end)
42
47
43
-
--- :GoTest
44
-
vim.api.nvim_create_user_command("GoTestAdd", function()
45
-
require("gopher").test.add()
46
-
end, { nargs = 0 })
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)
47
58
48
-
vim.api.nvim_create_user_command("GoTestsAll", function()
49
-
require("gopher").test.all()
50
-
end, { nargs = 0 })
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)
51
68
52
-
vim.api.nvim_create_user_command("GoTestsExp", function()
53
-
require("gopher").test.exported()
54
-
end, { nargs = 0 })
69
+
cmd("GoTagClear", function()
70
+
require("gopher").tags.clear()
71
+
end)
55
72
56
-
-- :Go
57
-
vim.api.nvim_create_user_command("GoMod", function(opts)
58
-
require("gopher").mod(opts.fargs)
59
-
end, { nargs = "*" })
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, "*")
60
78
61
-
vim.api.nvim_create_user_command("GoGet", function(opts)
62
-
vim.print(opts)
63
-
require("gopher").get(opts.fargs)
64
-
end, { nargs = "*" })
79
+
-- :GoTest
80
+
cmd("GoTestAdd", function()
81
+
require("gopher").test.add()
82
+
end)
65
83
66
-
vim.api.nvim_create_user_command("GoWork", function(opts)
67
-
require("gopher").get(opts.fargs)
68
-
end, { nargs = "*" })
84
+
cmd("GoTestsAll", function()
85
+
require("gopher").test.all()
86
+
end)
69
87
70
-
vim.api.nvim_create_user_command("GoGenerate", function(opts)
71
-
require("gopher").generate(opts.fargs or "")
72
-
end, { nargs = "?" })
73
-
end
88
+
cmd("GoTestsExp", function()
89
+
require("gopher").test.exported()
74
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, "?")
+6
scripts/docgen.lua
+6
scripts/docgen.lua
···
10
10
local files = {
11
11
"lua/gopher/init.lua",
12
12
"lua/gopher/config.lua",
13
+
"plugin/gopher.lua",
13
14
"lua/gopher/struct_tags.lua",
15
+
"lua/gopher/json2go.lua",
14
16
"lua/gopher/impl.lua",
15
17
"lua/gopher/gotests.lua",
16
18
"lua/gopher/iferr.lua",
···
27
29
table.remove(lines, 1)
28
30
29
31
return lines
32
+
end
33
+
34
+
hooks.sections["@dochide"] = function(s)
35
+
s.parent:clear_lines()
30
36
end
31
37
32
38
MiniDoc.generate(files, "doc/gopher.nvim.txt", { hooks = hooks })
+20
-3
scripts/minimal_init.lua
+20
-3
scripts/minimal_init.lua
···
20
20
end
21
21
end
22
22
23
-
install_plug "nvim-lua/plenary.nvim"
24
23
install_plug "nvim-treesitter/nvim-treesitter"
25
24
install_plug "echasnovski/mini.doc" -- used for docs generation
25
+
install_plug "folke/tokyonight.nvim" -- theme for generating demos
26
26
install_plug "echasnovski/mini.test"
27
27
28
28
vim.env.XDG_CONFIG_HOME = root ".tests/config"
···
30
30
vim.env.XDG_STATE_HOME = root ".tests/state"
31
31
vim.env.XDG_CACHE_HOME = root ".tests/cache"
32
32
33
-
vim.cmd [[set runtimepath=$VIMRUNTIME]]
34
33
vim.opt.runtimepath:append(root())
35
-
vim.opt.packpath = { root ".tests/site" }
34
+
vim.opt.packpath:append(root ".tests/site")
35
+
vim.o.swapfile = false
36
+
vim.o.writebackup = false
36
37
vim.notify = vim.print
37
38
38
39
-- install go treesitter parse
···
53
54
},
54
55
}
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
+
})
+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
+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/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
+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
+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
+38
-27
spec/integration/comment_test.lua
+38
-27
spec/integration/comment_test.lua
···
1
1
local t = require "spec.testutils"
2
-
3
-
local child = MiniTest.new_child_neovim()
4
-
local T = MiniTest.new_set {
5
-
hooks = {
6
-
post_once = child.stop,
7
-
pre_case = function()
8
-
child.restart { "-u", t.mininit_path }
9
-
end,
10
-
},
11
-
}
2
+
local child, T, comment = t.setup "comment"
12
3
13
4
local function do_the_test(fixture, pos)
14
-
local tmp = t.tmpfile()
15
-
local fixtures = t.get_fixtures("comment/" .. fixture)
16
-
t.writefile(tmp, fixtures.input)
17
-
18
-
child.cmd("silent edit " .. tmp)
19
-
child.fn.setpos(".", { child.fn.bufnr "%", unpack(pos) })
5
+
local rs = t.setup_test("comment/" .. fixture, child, pos)
20
6
child.cmd "GoCmt"
21
7
child.cmd "write"
22
8
23
-
t.eq(t.readfile(tmp), fixtures.output)
24
-
25
-
-- without it all other(not even from this module) tests are falling
26
-
t.deletefile(tmp)
9
+
t.eq(t.readfile(rs.tmp), rs.fixtures.output)
10
+
t.cleanup(rs)
27
11
end
28
12
29
-
T["comment"] = MiniTest.new_set {}
30
-
T["comment"]["should add comment to package"] = function()
13
+
comment["should add comment to package"] = function()
31
14
do_the_test("package", { 1, 1 })
32
15
end
33
16
34
-
T["comment"]["should add comment to struct"] = function()
17
+
comment["should add comment to struct"] = function()
35
18
do_the_test("struct", { 4, 1 })
36
19
end
37
20
38
-
T["comment"]["should add comment to function"] = function()
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()
39
34
do_the_test("func", { 3, 1 })
40
35
end
41
36
42
-
T["comment"]["should add comment to method"] = function()
37
+
comment["should add comment to method"] = function()
43
38
do_the_test("method", { 5, 1 })
44
39
end
45
40
46
-
T["comment"]["should add comment to interface"] = function()
41
+
comment["should add comment to interface"] = function()
47
42
do_the_test("interface", { 3, 6 })
48
43
end
49
44
50
-
T["comment"]["otherwise should add // above cursor"] = function()
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()
51
62
do_the_test("empty", { 1, 1 })
52
63
end
53
64
+9
-27
spec/integration/gotests_test.lua
+9
-27
spec/integration/gotests_test.lua
···
1
1
local t = require "spec.testutils"
2
-
3
-
local child = MiniTest.new_child_neovim()
4
-
local T = MiniTest.new_set {
5
-
hooks = {
6
-
post_once = child.stop,
7
-
pre_case = function()
8
-
child.restart { "-u", t.mininit_path }
9
-
end,
10
-
},
11
-
}
12
-
T["gotests"] = MiniTest.new_set {}
2
+
local child, T, gotests = t.setup "gotests"
13
3
14
4
--- NOTE: :GoTestAdd is the only place that has actual logic
15
5
--- All other parts are handled `gotests` itself.
···
20
10
return t.readfile(fpath:gsub(".go", "_test.go"))
21
11
end
22
12
23
-
T["gotests"]["should add test for function under cursor"] = function()
24
-
local tmp = t.tmpfile()
25
-
local fixtures = t.get_fixtures "tests/function"
26
-
t.writefile(tmp, fixtures.input)
27
-
28
-
child.cmd("silent edit " .. tmp)
29
-
child.fn.setpos(".", { child.fn.bufnr "%", 3, 6 })
13
+
gotests["should add test for function under cursor"] = function()
14
+
local rs = t.setup_test("tests/function", child, { 3, 5 })
30
15
child.cmd "GoTestAdd"
31
16
32
-
t.eq(fixtures.output, read_testfile(tmp))
17
+
t.eq(rs.fixtures.output, read_testfile(rs.tmp))
18
+
t.cleanup(rs)
33
19
end
34
20
35
-
T["gotests"]["should add test for method under cursor"] = function()
36
-
local tmp = t.tmpfile()
37
-
local fixtures = t.get_fixtures "tests/method"
38
-
t.writefile(tmp, fixtures.input)
39
-
40
-
child.cmd("silent edit " .. tmp)
41
-
child.fn.setpos(".", { child.fn.bufnr "%", 5, 19 })
21
+
gotests["should add test for method under cursor"] = function()
22
+
local rs = t.setup_test("tests/method", child, { 5, 19 })
42
23
child.cmd "GoTestAdd"
43
24
44
-
t.eq(fixtures.output, read_testfile(tmp))
25
+
t.eq(rs.fixtures.output, read_testfile(rs.tmp))
26
+
t.cleanup(rs)
45
27
end
46
28
47
29
return T
+14
-26
spec/integration/iferr_test.lua
+14
-26
spec/integration/iferr_test.lua
···
1
1
local t = require "spec.testutils"
2
-
3
-
local child = MiniTest.new_child_neovim()
4
-
local T = MiniTest.new_set {
5
-
hooks = {
6
-
post_once = child.stop,
7
-
pre_case = function()
8
-
child.restart { "-u", t.mininit_path }
9
-
end,
10
-
},
11
-
}
12
-
T["iferr"] = MiniTest.new_set {}
13
-
T["iferr"]["works"] = function()
14
-
local tmp = t.tmpfile()
15
-
local fixtures = t.get_fixtures "iferr/iferr"
16
-
t.writefile(tmp, fixtures.input)
2
+
local child, T, iferr = t.setup "iferr"
17
3
18
-
child.cmd("silent edit " .. tmp)
19
-
child.fn.setpos(".", { child.fn.bufnr "%", 8, 2, 0 })
4
+
iferr["should add if != nil {"] = function()
5
+
local rs = t.setup_test("iferr/iferr", child, { 8, 2 })
20
6
child.cmd "GoIfErr"
21
7
child.cmd "write"
22
8
23
-
t.eq(t.readfile(tmp), fixtures.output)
9
+
t.eq(t.readfile(rs.tmp), rs.fixtures.output)
10
+
t.cleanup(rs)
24
11
end
25
12
26
-
T["iferr"]["works with custom message"] = function()
27
-
local tmp = t.tmpfile()
28
-
local fixtures = t.get_fixtures "iferr/message"
29
-
t.writefile(tmp, fixtures.input)
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
+
]]
30
19
31
-
child.lua [[ require("gopher").setup { iferr = { message = 'fmt.Errorf("failed to %w", err)' } } ]]
32
-
child.cmd("silent edit " .. tmp)
33
-
child.fn.setpos(".", { child.fn.bufnr "%", 6, 2, 0 })
20
+
local rs = t.setup_test("iferr/message", child, { 6, 2 })
34
21
child.cmd "GoIfErr"
35
22
child.cmd "write"
36
23
37
-
t.eq(t.readfile(tmp), fixtures.output)
24
+
t.eq(t.readfile(rs.tmp), rs.fixtures.output)
25
+
t.cleanup(rs)
38
26
end
39
27
40
28
return T
+16
-36
spec/integration/impl_test.lua
+16
-36
spec/integration/impl_test.lua
···
1
1
local t = require "spec.testutils"
2
-
3
-
local child = MiniTest.new_child_neovim()
4
-
local T = MiniTest.new_set {
5
-
hooks = {
6
-
post_once = child.stop,
7
-
pre_case = function()
8
-
child.restart { "-u", t.mininit_path }
9
-
end,
10
-
},
11
-
}
12
-
T["impl"] = MiniTest.new_set {}
13
-
T["impl"]["works w io.Writer"] = function()
14
-
local tmp = t.tmpfile()
15
-
local fixtures = t.get_fixtures "impl/writer"
16
-
t.writefile(tmp, fixtures.input)
2
+
local child, T, impl = t.setup "impl"
17
3
18
-
child.cmd("silent edit " .. tmp)
19
-
child.fn.setpos(".", { child.fn.bufnr(tmp), 3, 0 })
4
+
impl["should do impl with 'w io.Writer'"] = function()
5
+
local rs = t.setup_test("impl/writer", child, { 3, 0 })
20
6
child.cmd "GoImpl w io.Writer"
21
7
child.cmd "write"
22
8
23
9
-- NOTE: since "impl" won't implement interface if it's already implemented i went with this hack
24
-
local rhs = fixtures.output:gsub("Test2", "Test")
25
-
t.eq(t.readfile(tmp), rhs)
10
+
local rhs = rs.fixtures.output:gsub("Test2", "Test")
11
+
t.eq(t.readfile(rs.tmp), rhs)
12
+
t.cleanup(rs)
26
13
end
27
14
28
-
T["impl"]["works r Read io.Reader"] = function()
29
-
local tmp = t.tmpfile()
30
-
local fixtures = t.get_fixtures "impl/reader"
31
-
t.writefile(tmp, fixtures.input)
32
-
33
-
child.cmd("silent edit " .. tmp)
15
+
impl["should work with full input, 'r Read io.Reader'"] = function()
16
+
local rs = t.setup_test("impl/reader", child)
34
17
child.cmd "GoImpl r Read io.Reader"
35
18
child.cmd "write"
36
19
37
-
local rhs = fixtures.output:gsub("Read2", "Read")
38
-
t.eq(t.readfile(tmp), rhs)
20
+
local rhs = rs.fixtures.output:gsub("Read2", "Read")
21
+
t.eq(t.readfile(rs.tmp), rhs)
22
+
t.cleanup(rs)
39
23
end
40
24
41
-
T["impl"]["works io.Closer"] = function()
42
-
local tmp = t.tmpfile()
43
-
local fixtures = t.get_fixtures "impl/closer"
44
-
t.writefile(tmp, fixtures.input)
45
-
46
-
child.cmd("silent edit " .. tmp)
47
-
child.fn.setpos(".", { child.fn.bufnr(tmp), 3, 6 })
25
+
impl["should work with minimal input 'io.Closer'"] = function()
26
+
local rs = t.setup_test("impl/closer", child, { 3, 6 })
48
27
child.cmd "GoImpl io.Closer"
49
28
child.cmd "write"
50
29
51
-
local rhs = fixtures.output:gsub("Test2", "Test")
52
-
t.eq(t.readfile(tmp), rhs)
30
+
local rhs = rs.fixtures.output:gsub("Test2", "Test")
31
+
t.eq(t.readfile(rs.tmp), rhs)
32
+
t.cleanup(rs)
53
33
end
54
34
55
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
+61
-1
spec/testutils.lua
+61
-1
spec/testutils.lua
···
6
6
testutils.mininit_path = vim.fs.joinpath(base_dir, "scripts", "minimal_init.lua")
7
7
testutils.fixtures_dir = vim.fs.joinpath(base_dir, "spec/fixtures")
8
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
+
9
28
---@generic T
10
29
---@param a T
11
30
---@param b T
···
36
55
vim.fn.delete(fpath)
37
56
end
38
57
58
+
---@class gopher.TestUtilsFixtures
59
+
---@field input string
60
+
---@field output string
61
+
39
62
---@param fixture string
40
-
---@return {input: string, output: string}
63
+
---@return gopher.TestUtilsFixtures
41
64
function testutils.get_fixtures(fixture)
42
65
return {
43
66
input = testutils.readfile(vim.fs.joinpath(testutils.fixtures_dir, fixture) .. "_input.go"),
44
67
output = testutils.readfile(vim.fs.joinpath(testutils.fixtures_dir, fixture) .. "_output.go"),
45
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)
46
106
end
47
107
48
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