···1818```bash
1919sudo pacman -S selene stylua
2020# or whatever is your package manager
2121-# or way of installing pkgs
2221```
23222423For formatting use this following commands, or setup your editor to integrate with selene/stylua:
2524```bash
2626-task format
2727-task format:check # will check if your code formatted
2828-task lint
2525+task stylua
2626+task lint # lintering and format chewing
2927```
30283129### Documentation
···3937```
40384139### Commit messages
4040+4241We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), please follow it.
43424443### Testing
45444646-For testing this plugins uses [plenary.nvim](https://github.com/nvim-lua/plenary.nvim).
4747-All tests live in [/spec](https://github.com/olexsmir/gopher.nvim/tree/main/spec) dir.
4545+For testing this plugins uses [mini.test](https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-test.md).
4646+All tests live in [/spec](./spec) dir.
48474948You can run tests with:
5049```bash
5151-task test
5252-# also there are some aliases for that
5350task tests
5454-task spec
5551```
+21
LICENSE
···11+MIT License
22+33+Copyright (c) 2025 Oleksandr Smirnov
44+55+Permission is hereby granted, free of charge, to any person obtaining a copy
66+of this software and associated documentation files (the "Software"), to deal
77+in the Software without restriction, including without limitation the rights
88+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+copies of the Software, and to permit persons to whom the Software is
1010+furnished to do so, subject to the following conditions:
1111+1212+The above copyright notice and this permission notice shall be included in all
1313+copies or substantial portions of the Software.
1414+1515+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+SOFTWARE.
+19-27
README.md
···10101111## Install (using [lazy.nvim](https://github.com/folke/lazy.nvim))
12121313-Pre-dependency:
1313+Requirements:
14141515-- [Go](https://github.com/golang/go)
1616-- `go` treesitter parser, install by `:TSInstall go`
1515+- **Neovim 0.10** or later
1616+- Treesitter `go` parser(`:TSInstall go`)
1717+- [Go](https://github.com/golang/go) installed (tested on 1.23)
17181819```lua
1920{
2021 "olexsmir/gopher.nvim",
2122 ft = "go",
2222- -- branch = "develop", -- if you want develop branch
2323- -- keep in mind, it might break everything
2323+ -- branch = "develop"
2424 dependencies = {
2525- "nvim-lua/plenary.nvim",
2625 "nvim-treesitter/nvim-treesitter",
2727- "mfussenegger/nvim-dap", -- (optional) only if you use `gopher.dap`
2826 },
2927 -- (optional) will update plugin's deps on every update
3028 build = function()
···3533}
3634```
37353838-## Configuratoin
3636+## Configuration
39374038> [!IMPORTANT]
4139>
4240> If you need more info look `:h gopher.nvim`
43414444-**Take a look at default options**
4242+**Take a look at default options (might be a bit outdated, look `:h gopher.nvim-config`)**
45434644```lua
4745require("gopher").setup {
4646+ -- log level, you might consider using DEBUG or TRACE for debugging the plugin
4747+ log_level = vim.log.levels.INFO,
4848+4949+ -- timeout for running internal commands
5050+ timeout = 2000,
5151+4852 commands = {
4953 go = "go",
5054 gomodifytags = "gomodifytags",
5155 gotests = "gotests",
5256 impl = "impl",
5357 iferr = "iferr",
5454- dlv = "dlv",
5558 },
5659 gotests = {
5760 -- gotests doesn't have template named "default" so this plugin uses "default" to set the default template
···5962 -- path to a directory containing custom test code templates
6063 template_dir = nil,
6164 -- switch table tests from using slice to map (with test name for the key)
6262- -- works only with gotests installed from develop branch
6365 named = false,
6466 },
6567 gotag = {
6668 transform = "snakecase",
6969+ -- default tags to add to struct fields
7070+ default_tag = "json",
7171+ },
7272+ iferr = {
7373+ -- choose a custom error message
7474+ message = nil,
6775 },
6876}
6977```
···8795 - [impl](https://github.com/josharian/impl)
8896 - [gotests](https://github.com/cweill/gotests)
8997 - [iferr](https://github.com/koron/iferr)
9090- - [dlv](github.com/go-delve/delve/cmd/dlv)
9198</details>
929993100<details>
···215222 ```
216223</details>
217224218218-<details>
219219- <summary>
220220- <b>Setup <a href="https://github.com/mfussenegger/nvim-dap">nvim-dap</a> for go in one line</b>
221221- </summary>
222222-223223- THIS FEATURE WILL BE REMOVED IN `0.1.6`
224224-225225- note [nvim-dap](https://github.com/mfussenegger/nvim-dap) has to be installed
226226-227227- ```lua
228228- require("gopher.dap").setup()
229229- ```
230230-</details>
231231-232225## Contributing
233226234227PRs are always welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md)
···236229## Thanks
237230238231- [go.nvim](https://github.com/ray-x/go.nvim)
239239-- [nvim-dap-go](https://github.com/leoluz/nvim-dap-go)
240232- [iferr](https://github.com/koron/iferr)
···11-*gopher.nvim*
11+*gopher.nvim* Enhance your golang experience
22+33+MIT License Copyright (c) 2025 Oleksandr Smirnov
2435==============================================================================
4657gopher.nvim is a minimalistic plugin for Go development in Neovim written in Lua.
68It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim.
7988-------------------------------------------------------------------------------
99- *gopher.nvim-table-of-contents*
1010Table of Contents
1111- Setup....................................................|gopher.nvim-setup|
1212- Install dependencies..............................|gopher.nvim-install-deps|
1313- Configuration...........................................|gopher.nvim-config|
1414- Modifty struct tags................................|gopher.nvim-struct-tags|
1515- Auto implementation of interface methods..................|gopher.nvim-impl|
1616- Generating unit tests boilerplate......................|gopher.nvim-gotests|
1717- Iferr....................................................|gopher.nvim-iferr|
1818- Generate comments.....................................|gopher.nvim-comments|
1919- Setup `nvim-dap` for Go......................................|gopher.nvim-dap|
1111+ Setup ................................................ |gopher.nvim-setup()|
1212+ Install dependencies ............................ |gopher.nvim-dependencies|
1313+ Config ................................................ |gopher.nvim-config|
1414+ Commands ............................................ |gopher.nvim-commands|
1515+ Modify struct tags ............................... |gopher.nvim-struct-tags|
1616+ Auto implementation of interface methods ................ |gopher.nvim-impl|
1717+ Generating unit tests boilerplate .................... |gopher.nvim-gotests|
1818+ Iferr .................................................. |gopher.nvim-iferr|
1919+ Generate comments ................................... |gopher.nvim-comments|
20202121------------------------------------------------------------------------------
2222- *gopher.nvim-setup*
2222+ *gopher.nvim-setup()*
2323 `gopher.setup`({user_config})
2424-Setup function. This method simply merges default configs with opts table.
2424+Setup function. This method simply merges default config with opts table.
2525You can read more about configuration at |gopher.nvim-config|
2626-Calling this function is optional, if you ok with default settings. Look |gopher.nvim.config-defaults|
2626+Calling this function is optional, if you ok with default settings.
2727+See |gopher.nvim.config|
27282829Usage ~
2929-`require("gopher").setup {}` (replace `{}` with your `config` table)
3030+>lua
3131+ require("gopher").setup {} -- use default config or replace {} with your own
3232+<
3033Parameters ~
3131-{user_config} gopher.Config
3434+{user_config} `(gopher.Config)` See |gopher.nvim-config|
32353336------------------------------------------------------------------------------
3434- *gopher.nvim-install-deps*
3737+ *gopher.nvim-dependencies*
3538 `gopher.install_deps`
3639Gopher.nvim implements most of its features using third-party tools.
3737-To install these tools, you can run `:GoInstallDeps` command
3838-or call `require("gopher").install_deps()` if you want ues lua api.
4040+To install these tools, you can run `:GoInstallDeps` command
4141+or call `require("gopher").install_deps()` if you want to use lua api.
4242+By default dependencies will be installed asynchronously,
4343+to install them synchronously pass `{sync = true}` as an argument.
394440454146==============================================================================
4247------------------------------------------------------------------------------
4348 *gopher.nvim-config*
4444-config it is the place where you can configure the plugin.
4545-also this is optional is you're ok with default settings.
4646-You can look at default options |gopher.nvim-config-defaults|
4747-4848-------------------------------------------------------------------------------
4949- *gopher.nvim-config-defaults*
5049 `default_config`
5150>lua
5251 local default_config = {
5353- --minidoc_replace_end
5454-5555- -- log level, you might consider using DEBUG or TRACE for degugging the plugin
5252+ -- log level, you might consider using DEBUG or TRACE for debugging the plugin
5653 ---@type number
5754 log_level = vim.log.levels.INFO,
5555+5656+ -- timeout for running internal commands
5757+ ---@type number
5858+ timeout = 2000,
58595960 -- user specified paths to binaries
6061 ---@class gopher.ConfigCommand
···6465 gotests = "gotests",
6566 impl = "impl",
6667 iferr = "iferr",
6767- dlv = "dlv",
6868 },
6969 ---@class gopher.ConfigGotests
7070 gotests = {
···7474 ---@type string|nil
7575 template_dir = nil,
7676 -- switch table tests from using slice to map (with test name for the key)
7777- -- works only with gotests installed from develop branch
7877 named = false,
7978 },
8079 ---@class gopher.ConfigGoTag
8180 gotag = {
8281 ---@type gopher.ConfigGoTagTransform
8382 transform = "snakecase",
8383+8484+ -- default tags to add to struct fields
8585+ default_tag = "json",
8686+ },
8787+ iferr = {
8888+ -- choose a custom error message
8989+ ---@type string|nil
9090+ message = nil,
8491 },
8592 }
8693<
···90979198==============================================================================
9299------------------------------------------------------------------------------
100100+ *gopher.nvim-commands*
101101+102102+If don't want to automatically register plugins' commands,
103103+you can set `vim.g.gopher_register_commands` to `false`, before loading the plugin.
104104+105105+106106+==============================================================================
107107+------------------------------------------------------------------------------
93108 *gopher.nvim-struct-tags*
9494-struct-tags is utilizing the `gomodifytags` tool to add or remove tags to struct fields.
109109+110110+`struct_tags` is utilizing the `gomodifytags` tool to add or remove tags to struct fields.
111111+95112Usage ~
9696-- put your coursor on the struct
9797-- run `:GoTagAdd json` to add json tags to struct fields
9898-- run `:GoTagRm json` to remove json tags to struct fields
113113+114114+How to add/remove tags to struct fields:
115115+1. Place cursor on the struct
116116+2. Run `:GoTagAdd json` to add json tags to struct fields
117117+3. Run `:GoTagRm json` to remove json tags to struct fields
118118+119119+To clear all tags from struct run: `:GoTagClear`
99120100100-note: if you dont spesify the tag it will use `json` as default
121121+NOTE: if you dont specify the tag it will use `json` as default
101122102102-simple example:
123123+Example:
103124>go
104125 // before
105126 type User struct {
···116137 }
117138<
118139119119-120140==============================================================================
121141------------------------------------------------------------------------------
122142 *gopher.nvim-impl*
123123-impl is utilizing the `impl` tool to generate method stubs for interfaces.
143143+144144+Integration of `impl` tool to generate method stubs for interfaces.
145145+124146Usage ~
147147+1. Automatically implement an interface for a struct:
148148+ - Place your cursor on the struct where you want to implement the interface.
149149+ - Run `:GoImpl io.Reader`
150150+ - This will automatically determine the receiver and implement the `io.Reader` interface.
125151126126-1. put your coursor on the struct on which you want implement the interface
127127- and run `:GoImpl io.Reader`
128128- which will automatically choose the reciver for the methods and
129129- implement the `io.Reader` interface
130130-2. same as previous but with custom receiver, so put your coursor on the struct
131131- run `:GoImpl w io.Writer`
132132- where `w` is the receiver and `io.Writer` is the interface
133133-3. specift receiver, struct, and interface
134134- there's no need to put your coursor on the struct if you specify all arguments
135135- `:GoImpl r RequestReader io.Reader`
136136- where `r` is the receiver, `RequestReader` is the struct and `io.Reader` is the interface
152152+2. Specify a custom receiver:
153153+ - Place your cursor on the struct
154154+ - Run `:GoImpl w io.Writer`, where:
155155+ - `w` is the receiver.
156156+ - `io.Writer` is the interface to implement.
157157+158158+3. Explicitly specify the receiver, struct, and interface:
159159+ - No need to place the cursor on the struct if all arguments are provided.
160160+ - Run `:GoImpl r RequestReader io.Reader`, where:
161161+ - `r` is the receiver.
162162+ - `RequestReader` is the struct.
163163+ - `io.Reader` is the interface to implement.
137164138138-simple example:
165165+Example:
139166>go
140167 type BytesReader struct{}
141168 // ^ put your cursor here
···143170144171 // this is what you will get
145172 func (b *BytesReader) Read(p []byte) (n int, err error) {
146146- panic("not implemented") // TODO: Implement
173173+ panic("not implemented") // TODO: Implement
147174 }
148175<
149149-150176151177==============================================================================
152178------------------------------------------------------------------------------
···154180gotests is utilizing the `gotests` tool to generate unit tests boilerplate.
155181Usage ~
156182157157-- generate unit test for spesisfic function/method
158158- - to specift the function/method put your cursor on it
159159- - run `:GoTestAdd`
183183+- Generate unit test for specific function/method:
184184+ 1. Place your cursor on the desired function/method.
185185+ 2. Run `:GoTestAdd`
160186161161-- generate unit tests for all functions/methods in current file
187187+- Generate unit tests for *all* functions/methods in current file:
162188 - run `:GoTestsAll`
163189164164-- generate unit tests only for exported(public) functions/methods
190190+- Generate unit tests *only* for *exported(public)* functions/methods:
165191 - run `:GoTestsExp`
166192167167-you can also specify the template to use for generating the tests. see |gopher.nvim-config|
168168-more details about templates can be found at: https://github.com/cweill/gotests
169169-170170-171171-------------------------------------------------------------------------------
172172- *gopher.nvim-gotests-named*
173173-174174-if you prefare using named tests, you can enable it in the config.
175175-but you would need to install `gotests@develop` because stable version doesn't support this feature.
176176-you can do it with:
177177->lua
178178- -- simply run go get in your shell:
179179- go install github.com/cweill/gotests/...@develop
193193+You can also specify the template to use for generating the tests. See |gopher.nvim-config|
194194+More details about templates can be found at: https://github.com/cweill/gotests
180195181181- -- if you want to install it within neovim, you can use one of this:
182182-183183- vim.fn.jobstart("go install github.com/cweill/gotests/...@develop")
184184-185185- -- or if you want to use mason:
186186- require("mason-tool-installer").setup {
187187- ensure_installed = {
188188- { "gotests", version = "develop" },
189189- }
190190- }
191191-<
192192-193193-if you choose to install `gotests` within neovim, i recommend adding it to your `build` section in your |lazy.nvim|
196196+If you prefer named tests, you can enable them in |gopher.nvim-config|.
194197195198196199==============================================================================
197200------------------------------------------------------------------------------
198201 *gopher.nvim-iferr*
199199-if you're using `iferr` tool, this module provides a way to automatically insert `if err != nil` check.
202202+203203+`iferr` provides a way to way to automatically insert `if err != nil` check.
204204+If you want to change `-message` option of `iferr` tool, see |gopher.nvim-config|
205205+200206Usage ~
201201-execute `:GoIfErr` near any err variable to insert the check
207207+Execute `:GoIfErr` near any `err` variable to insert the check
202208203209204210==============================================================================
205211------------------------------------------------------------------------------
206212 *gopher.nvim-comments*
207207-Usage ~
208208-Execute `:GoCmt` to generate a comment for the current function/method/struct/etc on this line.
213213+209214This module provides a way to generate comments for Go code.
210215211211-212212-==============================================================================
213213-------------------------------------------------------------------------------
214214- *gopher.nvim-dap*
215215-This module sets up `nvim-dap` for Go.
216216Usage ~
217217-just call `require("gopher.dap").setup()`, and you're good to go.
217217+Set cursor on line with function/method/struct/etc and run `:GoCmt` to generate a comment.
218218219219220220 vim:tw=78:ts=8:noet:ft=help:norl:
-33
lua/gopher/_utils/health_util.lua
···11-local h = vim.health or require "health"
22-local health = {}
33-44-health.start = h.start or h.report_start
55-health.ok = h.ok or h.report_ok
66-health.warn = h.warn or h.report_warn
77-health.error = h.error or h.report_error
88-health.info = h.info or h.report_info
99-1010----@param module string
1111----@return boolean
1212-function health.is_lualib_found(module)
1313- local is_found, _ = pcall(require, module)
1414- return is_found
1515-end
1616-1717----@param bin string
1818----@return boolean
1919-function health.is_binary_found(bin)
2020- if vim.fn.executable(bin) == 1 then
2121- return true
2222- end
2323- return false
2424-end
2525-2626----@param ft string
2727----@return boolean
2828-function health.is_treesitter_parser_available(ft)
2929- local ok, parser = pcall(vim.treesitter.get_parser, 0, ft)
3030- return ok and parser ~= nil
3131-end
3232-3333-return health
+24-17
lua/gopher/_utils/init.lua
···33local utils = {}
4455---@param msg string
66----@param lvl number
77-function utils.deferred_notify(msg, lvl)
88- vim.defer_fn(function()
99- vim.notify(msg, lvl, {
1010- title = c.___plugin_name,
1111- })
1212- log.debug(msg)
1313- end, 0)
1414-end
1515-1616----@param msg string
176---@param lvl? number
187function utils.notify(msg, lvl)
198 lvl = lvl or vim.log.levels.INFO
209 vim.notify(msg, lvl, {
1010+ ---@diagnostic disable-next-line:undefined-field
2111 title = c.___plugin_name,
2212 })
2313 log.debug(msg)
2414end
25152626--- safe require
2727----@param module string module name
2828-function utils.sreq(module)
2929- local ok, m = pcall(require, module)
3030- assert(ok, string.format("gopher.nvim dependency error: %s not installed", module))
3131- return m
1616+---@param path string
1717+---@return string
1818+function utils.readfile_joined(path)
1919+ return table.concat(vim.fn.readfile(path), "\n")
2020+end
2121+2222+---@param t string[]
2323+---@return string[]
2424+function utils.remove_empty_lines(t)
2525+ local res = {}
2626+ for _, line in ipairs(t) do
2727+ if line ~= "" then
2828+ table.insert(res, line)
2929+ end
3030+ end
3131+ return res
3232+end
3333+3434+---@param s string
3535+---@return string
3636+function utils.trimend(s)
3737+ local r, _ = string.gsub(s, "%s+$", "")
3838+ return r
3239end
33403441return utils
+2-1
lua/gopher/_utils/log.lua
···18181919local config = {
2020 -- Name of the plugin. Prepended to log messages
2121+ ---@diagnostic disable-next-line:undefined-field
2122 name = c.___plugin_name,
22232324 -- Should print the output to neovim while running
···9192 local log_at_level = function(level_config, message_maker, ...)
9293 -- Return early if we're below the current_log_level
9394 --
9494- -- the log level source get from config directly because otherwise it doesnt work
9595+ -- the log level source get from config directly because otherwise it doesn't work
9596 if level_config.level < c.log_level then
9697 return
9798 end
+39
lua/gopher/_utils/runner.lua
···11+local c = require "gopher.config"
22+local runner = {}
33+44+---@class gopher.RunnerOpts
55+---@field cwd? string
66+---@field timeout? number
77+---@field stdin? boolean|string|string[]
88+---@field text? boolean
99+1010+---@param cmd (string|number)[]
1111+---@param on_exit fun(out:vim.SystemCompleted)
1212+---@param opts? gopher.RunnerOpts
1313+---@return vim.SystemObj
1414+function runner.async(cmd, on_exit, opts)
1515+ opts = opts or {}
1616+ return vim.system(cmd, {
1717+ cwd = opts.cwd or nil,
1818+ timeout = opts.timeout or c.timeout,
1919+ stdin = opts.stdin or nil,
2020+ text = opts.text or true,
2121+ }, on_exit)
2222+end
2323+2424+---@param cmd (string|number)[]
2525+---@param opts? gopher.RunnerOpts
2626+---@return vim.SystemCompleted
2727+function runner.sync(cmd, opts)
2828+ opts = opts or {}
2929+ return vim
3030+ .system(cmd, {
3131+ cwd = opts.cwd or nil,
3232+ timeout = opts.timeout or c.timeout,
3333+ stdin = opts.stdin or nil,
3434+ text = opts.text or true,
3535+ })
3636+ :wait()
3737+end
3838+3939+return runner
···11+local ts = {}
22+local queries = {
33+ struct = [[
44+ [(type_spec name: (type_identifier) @_name
55+ type: (struct_type))
66+ (var_declaration (var_spec
77+ name: (identifier) @_name @_var
88+ type: (struct_type)))
99+ (short_var_declaration
1010+ left: (expression_list (identifier) @_name @_var)
1111+ right: (expression_list (composite_literal
1212+ type: (struct_type))))]
1313+ ]],
1414+ func = [[
1515+ [(function_declaration name: (identifier) @_name)
1616+ (method_declaration name: (field_identifier) @_name)]
1717+ ]],
1818+ package = [[
1919+ (package_identifier) @_name
2020+ ]],
2121+ interface = [[
2222+ (type_spec
2323+ name: (type_identifier) @_name
2424+ type: (interface_type))
2525+ ]],
2626+}
2727+2828+---@param parent_type string[]
2929+---@param node TSNode
3030+---@return TSNode?
3131+local function get_parrent_node(parent_type, node)
3232+ ---@type TSNode?
3333+ local current = node
3434+ while current do
3535+ if vim.tbl_contains(parent_type, current:type()) then
3636+ break
3737+ end
3838+3939+ current = current:parent()
4040+ if current == nil then
4141+ return nil
4242+ end
4343+ end
4444+ return current
4545+end
4646+4747+---@param query vim.treesitter.Query
4848+---@param node TSNode
4949+---@param bufnr integer
5050+---@return {name:string, is_varstruct:boolean}
5151+local function get_captures(query, node, bufnr)
5252+ local res = {}
5353+ for id, _node in query:iter_captures(node, bufnr) do
5454+ if query.captures[id] == "_name" then
5555+ res["name"] = vim.treesitter.get_node_text(_node, bufnr)
5656+ end
5757+5858+ if query.captures[id] == "_var" then
5959+ res["is_varstruct"] = true
6060+ end
6161+ end
6262+6363+ return res
6464+end
6565+6666+---@class gopher.TsResult
6767+---@field name string
6868+---@field start_line integer
6969+---@field end_line integer
7070+---@field is_varstruct boolean
7171+7272+---@param bufnr integer
7373+---@param parent_type string[]
7474+---@param query string
7575+---@return gopher.TsResult
7676+local function do_stuff(bufnr, parent_type, query)
7777+ if not vim.treesitter.get_parser(bufnr, "go") then
7878+ error "No treesitter parser found for go"
7979+ end
8080+8181+ local node = vim.treesitter.get_node {
8282+ bufnr = bufnr,
8383+ }
8484+ if not node then
8585+ error "No nodes found under cursor"
8686+ end
8787+8888+ local parent_node = get_parrent_node(parent_type, node)
8989+ if not parent_node then
9090+ error "No parent node found under cursor"
9191+ end
9292+9393+ local q = vim.treesitter.query.parse("go", query)
9494+ local res = get_captures(q, parent_node, bufnr)
9595+ assert(res.name ~= nil, "No capture name found")
9696+9797+ local start_row, _, end_row, _ = parent_node:range()
9898+ res["start_line"] = start_row + 1
9999+ res["end_line"] = end_row + 1
100100+101101+ return res
102102+end
103103+104104+---@param bufnr integer
105105+function ts.get_struct_under_cursor(bufnr)
106106+ --- should be both type_spec and type_declaration
107107+ --- because in cases like `type ( T struct{}, U strict{} )`
108108+ --- i will be choosing always last struct in the list
109109+ ---
110110+ --- var_declaration is for cases like `var x struct{}`
111111+ --- short_var_declaration is for cases like `x := struct{}{}`
112112+ return do_stuff(bufnr, {
113113+ "type_spec",
114114+ "type_declaration",
115115+ "var_declaration",
116116+ "short_var_declaration",
117117+ }, queries.struct)
118118+end
119119+120120+---@param bufnr integer
121121+function ts.get_func_under_cursor(bufnr)
122122+ --- since this handles both and funcs and methods we should check for both parent nodes
123123+ return do_stuff(bufnr, { "function_declaration", "method_declaration" }, queries.func)
124124+end
125125+126126+---@param bufnr integer
127127+function ts.get_package_under_cursor(bufnr)
128128+ return do_stuff(bufnr, { "package_clause" }, queries.package)
129129+end
130130+131131+---@param bufnr integer
132132+function ts.get_interface_under_cursor(bufnr)
133133+ return do_stuff(bufnr, { "type_declaration" }, queries.interface)
134134+end
135135+136136+return ts
-104
lua/gopher/_utils/ts/init.lua
···11----@diagnostic disable: param-type-mismatch
22-local nodes = require "gopher._utils.ts.nodes"
33-local u = require "gopher._utils"
44-local ts = {
55- querys = {
66- struct_block = [[((type_declaration (type_spec name:(type_identifier) @struct.name type: (struct_type)))@struct.declaration)]],
77- em_struct_block = [[(field_declaration name:(field_identifier)@struct.name type: (struct_type)) @struct.declaration]],
88- package = [[(package_clause (package_identifier)@package.name)@package.clause]],
99- interface = [[((type_declaration (type_spec name:(type_identifier) @interface.name type:(interface_type)))@interface.declaration)]],
1010- method_name = [[((method_declaration receiver: (parameter_list)@method.receiver name: (field_identifier)@method.name body:(block))@method.declaration)]],
1111- func = [[((function_declaration name: (identifier)@function.name) @function.declaration)]],
1212- },
1313-}
1414-1515----@return table
1616-local function get_name_defaults()
1717- return {
1818- ["func"] = "function",
1919- ["if"] = "if",
2020- ["else"] = "else",
2121- ["for"] = "for",
2222- }
2323-end
2424-2525----@param row string
2626----@param col string
2727----@param bufnr string|nil
2828----@param do_notify boolean|nil
2929----@return table|nil
3030-function ts.get_struct_node_at_pos(row, col, bufnr, do_notify)
3131- local notify = do_notify or true
3232- local query = ts.querys.struct_block .. " " .. ts.querys.em_struct_block
3333- local bufn = bufnr or vim.api.nvim_get_current_buf()
3434- local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col)
3535- if ns == nil then
3636- if notify then
3737- u.deferred_notify("struct not found", vim.log.levels.WARN)
3838- end
3939- else
4040- return ns[#ns]
4141- end
4242-end
4343-4444----@param row string
4545----@param col string
4646----@param bufnr string|nil
4747----@param do_notify boolean|nil
4848----@return table|nil
4949-function ts.get_func_method_node_at_pos(row, col, bufnr, do_notify)
5050- local notify = do_notify or true
5151- local query = ts.querys.func .. " " .. ts.querys.method_name
5252- local bufn = bufnr or vim.api.nvim_get_current_buf()
5353- local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col)
5454- if ns == nil then
5555- if notify then
5656- u.deferred_notify("function not found", vim.log.levels.WARN)
5757- end
5858- else
5959- return ns[#ns]
6060- end
6161-end
6262-6363----@param row string
6464----@param col string
6565----@param bufnr string|nil
6666----@param do_notify boolean|nil
6767----@return table|nil
6868-function ts.get_package_node_at_pos(row, col, bufnr, do_notify)
6969- local notify = do_notify or true
7070- -- stylua: ignore
7171- if row > 10 then return end
7272- local query = ts.querys.package
7373- local bufn = bufnr or vim.api.nvim_get_current_buf()
7474- local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col)
7575- if ns == nil then
7676- if notify then
7777- u.deferred_notify("package not found", vim.log.levels.WARN)
7878- return nil
7979- end
8080- else
8181- return ns[#ns]
8282- end
8383-end
8484-8585----@param row string
8686----@param col string
8787----@param bufnr string|nil
8888----@param do_notify boolean|nil
8989----@return table|nil
9090-function ts.get_interface_node_at_pos(row, col, bufnr, do_notify)
9191- local notify = do_notify or true
9292- local query = ts.querys.interface
9393- local bufn = bufnr or vim.api.nvim_get_current_buf()
9494- local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col)
9595- if ns == nil then
9696- if notify then
9797- u.deferred_notify("interface not found", vim.log.levels.WARN)
9898- end
9999- else
100100- return ns[#ns]
101101- end
102102-end
103103-104104-return ts
-143
lua/gopher/_utils/ts/nodes.lua
···11-local ts_query = require "nvim-treesitter.query"
22-local parsers = require "nvim-treesitter.parsers"
33-local locals = require "nvim-treesitter.locals"
44-local u = require "gopher._utils"
55-local M = {}
66-77-local function intersects(row, col, sRow, sCol, eRow, eCol)
88- if sRow > row or eRow < row then
99- return false
1010- end
1111-1212- if sRow == row and sCol > col then
1313- return false
1414- end
1515-1616- if eRow == row and eCol < col then
1717- return false
1818- end
1919-2020- return true
2121-end
2222-2323----@param nodes table
2424----@param row string
2525----@param col string
2626----@return table
2727-function M.intersect_nodes(nodes, row, col)
2828- local found = {}
2929- for idx = 1, #nodes do
3030- local node = nodes[idx]
3131- local sRow = node.dim.s.r
3232- local sCol = node.dim.s.c
3333- local eRow = node.dim.e.r
3434- local eCol = node.dim.e.c
3535-3636- if intersects(row, col, sRow, sCol, eRow, eCol) then
3737- table.insert(found, node)
3838- end
3939- end
4040-4141- return found
4242-end
4343-4444----@param nodes table
4545----@return table
4646-function M.sort_nodes(nodes)
4747- table.sort(nodes, function(a, b)
4848- return M.count_parents(a) < M.count_parents(b)
4949- end)
5050-5151- return nodes
5252-end
5353-5454----@param query string
5555----@param lang string
5656----@param bufnr integer
5757----@param pos_row string
5858----@return string
5959-function M.get_all_nodes(query, lang, _, bufnr, pos_row, _)
6060- bufnr = bufnr or 0
6161- pos_row = pos_row or 30000
6262-6363- local ok, parsed_query = pcall(function()
6464- return vim.treesitter.query.parse(lang, query)
6565- end)
6666- if not ok then
6767- return nil
6868- end
6969-7070- local parser = parsers.get_parser(bufnr, lang)
7171- local root = parser:parse()[1]:root()
7272- local start_row, _, end_row, _ = root:range()
7373- local results = {}
7474-7575- for match in ts_query.iter_prepared_matches(parsed_query, root, bufnr, start_row, end_row) do
7676- local sRow, sCol, eRow, eCol, declaration_node
7777- local type, name, op = "", "", ""
7878- locals.recurse_local_nodes(match, function(_, node, path)
7979- local idx = string.find(path, ".[^.]*$")
8080- op = string.sub(path, idx + 1, #path)
8181- type = string.sub(path, 1, idx - 1)
8282-8383- if op == "name" then
8484- name = vim.treesitter.get_node_text(node, bufnr)
8585- elseif op == "declaration" or op == "clause" then
8686- declaration_node = node
8787- sRow, sCol, eRow, eCol = node:range()
8888- sRow = sRow + 1
8989- eRow = eRow + 1
9090- sCol = sCol + 1
9191- eCol = eCol + 1
9292- end
9393- end)
9494-9595- if declaration_node ~= nil then
9696- table.insert(results, {
9797- declaring_node = declaration_node,
9898- dim = { s = { r = sRow, c = sCol }, e = { r = eRow, c = eCol } },
9999- name = name,
100100- operator = op,
101101- type = type,
102102- })
103103- end
104104- end
105105-106106- return results
107107-end
108108-109109----@param query string
110110----@param default string
111111----@param bufnr string
112112----@param row string
113113----@param col string
114114----@return table
115115-function M.nodes_at_cursor(query, default, bufnr, row, col)
116116- bufnr = bufnr or vim.api.nvim_get_current_buf()
117117- local ft = vim.api.nvim_buf_get_option(bufnr, "ft")
118118- if row == nil or col == nil then
119119- row, col = unpack(vim.api.nvim_win_get_cursor(0))
120120- end
121121-122122- local nodes = M.get_all_nodes(query, ft, default, bufnr, row, col)
123123- if nodes == nil then
124124- u.deferred_notify(
125125- "Unable to find any nodes. Place your cursor on a go symbol and try again",
126126- vim.log.levels.DEBUG
127127- )
128128- return nil
129129- end
130130-131131- nodes = M.sort_nodes(M.intersect_nodes(nodes, row, col))
132132- if nodes == nil or #nodes == 0 then
133133- u.deferred_notify(
134134- "Unable to find any nodes at pos. " .. tostring(row) .. ":" .. tostring(col),
135135- vim.log.levels.DEBUG
136136- )
137137- return nil
138138- end
139139-140140- return nodes
141141-end
142142-143143-return M
+39-41
lua/gopher/comment.lua
···11---@toc_entry Generate comments
22---@tag gopher.nvim-comments
33----@usage Execute `:GoCmt` to generate a comment for the current function/method/struct/etc on this line.
44----@text This module provides a way to generate comments for Go code.
33+---@text
44+--- This module provides a way to generate comments for Go code.
55+---
66+---@usage Set cursor on line with function/method/struct/etc and run `:GoCmt` to generate a comment.
5788+local ts = require "gopher._utils.ts"
69local log = require "gopher._utils.log"
1010+local comment = {}
71188-local function generate(row, col)
99- local ts_utils = require "gopher._utils.ts"
1010- local comment, ns = nil, nil
1212+---@param name string
1313+---@return string
1414+---@dochide
1515+local function template(name)
1616+ return "// " .. name .. " "
1717+end
11181212- ns = ts_utils.get_package_node_at_pos(row, col, nil, false)
1313- if ns ~= nil then
1414- comment = "// Package " .. ns.name .. " provides " .. ns.name
1515- return comment, ns
1919+---@param bufnr integer
2020+---@return string
2121+---@dochide
2222+local function generate(bufnr)
2323+ local s_ok, s_res = pcall(ts.get_struct_under_cursor, bufnr)
2424+ if s_ok then
2525+ return template(s_res.name)
1626 end
17271818- ns = ts_utils.get_struct_node_at_pos(row, col, nil, false)
1919- if ns ~= nil then
2020- comment = "// " .. ns.name .. " " .. ns.type .. " "
2121- return comment, ns
2828+ local f_ok, f_res = pcall(ts.get_func_under_cursor, bufnr)
2929+ if f_ok then
3030+ return template(f_res.name)
2231 end
23322424- ns = ts_utils.get_func_method_node_at_pos(row, col, nil, false)
2525- if ns ~= nil then
2626- comment = "// " .. ns.name .. " " .. ns.type .. " "
2727- return comment, ns
3333+ local i_ok, i_res = pcall(ts.get_interface_under_cursor, bufnr)
3434+ if i_ok then
3535+ return template(i_res.name)
2836 end
29373030- ns = ts_utils.get_interface_node_at_pos(row, col, nil, false)
3131- if ns ~= nil then
3232- comment = "// " .. ns.name .. " " .. ns.type .. " "
3333- return comment, ns
3838+ local p_ok, p_res = pcall(ts.get_package_under_cursor, bufnr)
3939+ if p_ok then
4040+ return "// Package " .. p_res.name .. " provides "
3441 end
35423636- return "// ", {}
4343+ return "// "
3744end
38453939-return function()
4040- local row, col = unpack(vim.api.nvim_win_get_cursor(0))
4141- local comment, ns = generate(row + 1, col + 1)
4242-4343- log.debug("generated comment: " .. comment)
4444-4545- vim.api.nvim_win_set_cursor(0, {
4646- ns.dim.s.r,
4747- ns.dim.s.c,
4848- })
4949-5050- ---@diagnostic disable-next-line: param-type-mismatch
5151- vim.fn.append(row - 1, comment)
5252-5353- vim.api.nvim_win_set_cursor(0, {
5454- ns.dim.s.r,
5555- #comment + 1,
5656- })
4646+function comment.comment()
4747+ local bufnr = vim.api.nvim_get_current_buf()
4848+ local cmt = generate(bufnr)
4949+ log.debug("generated comment: " .. cmt)
57505858- vim.cmd [[startinsert!]]
5151+ local pos = vim.fn.getcurpos()[2]
5252+ vim.fn.append(pos - 1, cmt)
5353+ vim.fn.setpos(".", { 0, pos, #cmt })
5454+ vim.cmd "startinsert!"
5955end
5656+5757+return comment
+46-25
lua/gopher/config.lua
···11----@toc_entry Configuration
22----@tag gopher.nvim-config
33----@text config it is the place where you can configure the plugin.
44---- also this is optional is you're ok with default settings.
55---- You can look at default options |gopher.nvim-config-defaults|
66-77----@type gopher.Config
88----@private
91local config = {}
102113---@tag gopher.nvim-config.ConfigGoTagTransform
124---@text Possible values for |gopher.Config|.gotag.transform:
135---
1414----@private
66+---@dochide
157---@alias gopher.ConfigGoTagTransform
168---| "snakecase" "GopherUser" -> "gopher_user"
179---| "camelcase" "GopherUser" -> "gopherUser"
···2012---| "titlecase" "GopherUser" -> "Gopher User"
2113---| "keep" keeps the original field name
22142323---minidoc_replace_start {
2424-2525----@tag gopher.nvim-config-defaults
2626----@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section):gsub(">", ">lua")
2727----
1515+---@toc_entry Config
1616+---@tag gopher.nvim-config
1717+---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
2818---@class gopher.Config
2919local default_config = {
3030- --minidoc_replace_end
3131-3232- -- log level, you might consider using DEBUG or TRACE for degugging the plugin
2020+ -- log level, you might consider using DEBUG or TRACE for debugging the plugin
3321 ---@type number
3422 log_level = vim.log.levels.INFO,
2323+2424+ -- timeout for running internal commands
2525+ ---@type number
2626+ timeout = 2000,
35273628 -- user specified paths to binaries
3729 ---@class gopher.ConfigCommand
···4133 gotests = "gotests",
4234 impl = "impl",
4335 iferr = "iferr",
4444- dlv = "dlv",
4536 },
4637 ---@class gopher.ConfigGotests
4738 gotests = {
···5142 ---@type string|nil
5243 template_dir = nil,
5344 -- switch table tests from using slice to map (with test name for the key)
5454- -- works only with gotests installed from develop branch
5545 named = false,
5646 },
5747 ---@class gopher.ConfigGoTag
5848 gotag = {
5949 ---@type gopher.ConfigGoTagTransform
6050 transform = "snakecase",
5151+5252+ -- default tags to add to struct fields
5353+ default_tag = "json",
5454+ },
5555+ iferr = {
5656+ -- choose a custom error message
5757+ ---@type string|nil
5858+ message = nil,
6159 },
6260}
6361--minidoc_afterlines_end
64626563---@type gopher.Config
6666----@private
6464+---@dochide
6765local _config = default_config
68666969--- I am kinda secret so don't tell anyone about me
7070--- even dont use me
6767+-- I am kinda secret so don't tell anyone about me even dont use me
7168--
7272--- if you don't belive me that i am secret see
6969+-- if you don't believe me that i am secret see
7370-- the line below it says @private
7471---@private
7572_config.___plugin_name = "gopher.nvim" ---@diagnostic disable-line: inject-field
76737774---@param user_config? gopher.Config
7878----@private
7575+---@dochide
7976function config.setup(user_config)
8080- _config = vim.tbl_deep_extend("force", default_config, user_config or {})
7777+ vim.validate { user_config = { user_config, "table", true } }
7878+7979+ _config = vim.tbl_deep_extend("force", vim.deepcopy(default_config), user_config or {})
8080+8181+ vim.validate {
8282+ log_level = { _config.log_level, "number" },
8383+ timeout = { _config.timeout, "number" },
8484+ ["commands"] = { _config.commands, "table" },
8585+ ["commands.go"] = { _config.commands.go, "string" },
8686+ ["commands.gomodifytags"] = { _config.commands.gomodifytags, "string" },
8787+ ["commands.gotests"] = { _config.commands.gotests, "string" },
8888+ ["commands.impl"] = { _config.commands.impl, "string" },
8989+ ["commands.iferr"] = { _config.commands.iferr, "string" },
9090+ ["gotests"] = { _config.gotests, "table" },
9191+ ["gotests.template"] = { _config.gotests.template, "string" },
9292+ ["gotests.template_dir"] = { _config.gotests.template, "string", true },
9393+ ["gotests.named"] = { _config.gotests.named, "boolean" },
9494+ ["gotag"] = { _config.gotag, "table" },
9595+ ["gotag.transform"] = { _config.gotag.transform, "string" },
9696+ ["gotag.default_tag"] = { _config.gotag.default_tag, "string" },
9797+ ["iferr"] = { _config.iferr, "table" },
9898+ ["iferr.message"] = { _config.iferr.message, "string", true },
9999+ }
81100end
8210183102setmetatable(config, {
···86105 end,
87106})
88107108108+---@dochide
109109+---@return gopher.Config
89110return config
-129
lua/gopher/dap.lua
···11----@toc_entry Setup `nvim-dap` for Go
22----@tag gopher.nvim-dap
33----@text This module sets up `nvim-dap` for Go.
44----@usage just call `require("gopher.dap").setup()`, and you're good to go.
55-66-local c = require "gopher.config"
77-local dap = {}
88-99-dap.adapter = function(callback, config)
1010- local host = config.host or "127.0.0.1"
1111- local port = config.port or "38697"
1212- local addr = string.format("%s:%s", host, port)
1313-1414- local handle, pid_or_err
1515- local stdout = assert(vim.loop.new_pipe(false))
1616- local opts = {
1717- stdio = { nil, stdout },
1818- args = { "dap", "-l", addr },
1919- detached = true,
2020- }
2121-2222- handle, pid_or_err = vim.loop.spawn(c.commands.dlv, opts, function(status)
2323- if not stdout or not handle then
2424- return
2525- end
2626-2727- stdout:close()
2828- handle:close()
2929- if status ~= 0 then
3030- print("dlv exited with code", status)
3131- end
3232- end)
3333-3434- assert(handle, "Error running dlv: " .. tostring(pid_or_err))
3535- if stdout then
3636- stdout:read_start(function(err, chunk)
3737- assert(not err, err)
3838- if chunk then
3939- vim.schedule(function()
4040- require("dap.repl").append(chunk)
4141- end)
4242- end
4343- end)
4444- end
4545-4646- -- wait for delve to start
4747- vim.defer_fn(function()
4848- callback { type = "server", host = "127.0.0.1", port = port }
4949- end, 100)
5050-end
5151-5252-local function args_input()
5353- vim.ui.input({ prompt = "Args: " }, function(input)
5454- return vim.split(input or "", " ")
5555- end)
5656-end
5757-5858-local function get_arguments()
5959- local co = coroutine.running()
6060- if co then
6161- return coroutine.create(function()
6262- local args = args_input()
6363- coroutine.resume(co, args)
6464- end)
6565- else
6666- return args_input()
6767- end
6868-end
6969-7070-dap.configuration = {
7171- {
7272- type = "go",
7373- name = "Debug",
7474- request = "launch",
7575- program = "${file}",
7676- },
7777- {
7878- type = "go",
7979- name = "Debug (Arguments)",
8080- request = "launch",
8181- program = "${file}",
8282- args = get_arguments,
8383- },
8484- {
8585- type = "go",
8686- name = "Debug Package",
8787- request = "launch",
8888- program = "${fileDirname}",
8989- },
9090- {
9191- type = "go",
9292- name = "Attach",
9393- mode = "local",
9494- request = "attach",
9595- processId = require("dap.utils").pick_process,
9696- },
9797- {
9898- type = "go",
9999- name = "Debug test",
100100- request = "launch",
101101- mode = "test",
102102- program = "${file}",
103103- },
104104- {
105105- type = "go",
106106- name = "Debug test (go.mod)",
107107- request = "launch",
108108- mode = "test",
109109- program = "./${relativeFileDirname}",
110110- },
111111-}
112112-113113--- sets ups nvim-dap for Go in one function call.
114114-function dap.setup()
115115- vim.deprecate(
116116- "gopher.dap",
117117- "you might consider setting up `nvim-dap` manually, or using another plugin(https://github.com/leoluz/nvim-dap-go)",
118118- "v0.1.6",
119119- "gopher"
120120- )
121121-122122- local ok, d = pcall(require, "dap")
123123- assert(ok, "gopher.nvim dependency error: dap not installed")
124124-125125- d.adapters.go = dap.adapter
126126- d.configurations.go = dap.configuration
127127-end
128128-129129-return dap
+17-46
lua/gopher/gotests.lua
···22---@tag gopher.nvim-gotests
33---@text gotests is utilizing the `gotests` tool to generate unit tests boilerplate.
44---@usage
55---- - generate unit test for spesisfic function/method
66---- - to specift the function/method put your cursor on it
77---- - run `:GoTestAdd`
55+--- - Generate unit test for specific function/method:
66+--- 1. Place your cursor on the desired function/method.
77+--- 2. Run `:GoTestAdd`
88---
99---- - generate unit tests for all functions/methods in current file
99+--- - Generate unit tests for *all* functions/methods in current file:
1010--- - run `:GoTestsAll`
1111---
1212---- - generate unit tests only for exported(public) functions/methods
1212+--- - Generate unit tests *only* for *exported(public)* functions/methods:
1313--- - run `:GoTestsExp`
1414---
1515---- you can also specify the template to use for generating the tests. see |gopher.nvim-config|
1616---- more details about templates can be found at: https://github.com/cweill/gotests
1515+--- You can also specify the template to use for generating the tests. See |gopher.nvim-config|
1616+--- More details about templates can be found at: https://github.com/cweill/gotests
1717---
1818-1919----@tag gopher.nvim-gotests-named
2020----@text
2121---- if you prefare using named tests, you can enable it in the config.
2222---- but you would need to install `gotests@develop` because stable version doesn't support this feature.
2323---- you can do it with:
2424---- >lua
2525---- -- simply run go get in your shell:
2626---- go install github.com/cweill/gotests/...@develop
2727----
2828---- -- if you want to install it within neovim, you can use one of this:
2929----
3030---- vim.fn.jobstart("go install github.com/cweill/gotests/...@develop")
3131----
3232---- -- or if you want to use mason:
3333---- require("mason-tool-installer").setup {
3434---- ensure_installed = {
3535---- { "gotests", version = "develop" },
3636---- }
3737---- }
3838---- <
3939----
4040---- if you choose to install `gotests` within neovim, i recommend adding it to your `build` section in your |lazy.nvim|
1818+--- If you prefer named tests, you can enable them in |gopher.nvim-config|.
41194220local c = require "gopher.config"
4321local ts_utils = require "gopher._utils.ts"
···4725local gotests = {}
48264927---@param args table
5050----@private
2828+---@dochide
5129local function add_test(args)
5230 if c.gotests.named then
5331 table.insert(args, "-named")
···68466947 log.debug("generating tests with args: ", args)
70487171- return r.sync(c.commands.gotests, {
7272- args = args,
7373- on_exit = function(data, status)
7474- if not status == 0 then
7575- error("gotests failed: " .. data)
7676- end
4949+ local rs = r.sync { c.commands.gotests, unpack(args) }
5050+ if rs.code ~= 0 then
5151+ error("gotests failed: " .. rs.stderr)
5252+ end
77537878- u.notify "unit test(s) generated"
7979- end,
8080- })
5454+ u.notify "unit test(s) generated"
8155end
82568357-- generate unit test for one function
8458function gotests.func_test()
8585- local ns = ts_utils.get_func_method_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0)))
8686- if ns == nil or ns.name == nil then
8787- u.notify("cursor on func/method and execute the command again", vim.log.levels.WARN)
8888- return
8989- end
5959+ local bufnr = vim.api.nvim_get_current_buf()
6060+ local func = ts_utils.get_func_under_cursor(bufnr)
90619191- add_test { "-only", ns.name }
6262+ add_test { "-only", func.name }
9263end
93649465-- generate unit tests for all functions in current file
+40-28
lua/gopher/health.lua
···11local health = {}
22local cmd = require("gopher.config").commands
33-local u = require "gopher._utils.health_util"
4354local deps = {
65 plugin = {
77- { lib = "dap", msg = "required for `gopher.dap`", optional = true },
88- { lib = "plenary", msg = "required for everyting in gopher.nvim", optional = false },
99- { lib = "nvim-treesitter", msg = "required for everyting in gopher.nvim", optional = false },
66+ { lib = "nvim-treesitter", msg = "required for everything in gopher.nvim" },
107 },
118 bin = {
129 {
···1411 msg = "required for `:GoGet`, `:GoMod`, `:GoGenerate`, `:GoWork`, `:GoInstallDeps`",
1512 optional = false,
1613 },
1717- { bin = cmd.gomodifytags, msg = "required for `:GoTagAdd`, `:GoTagRm`", optional = false },
1818- { bin = cmd.impl, msg = "required for `:GoImpl`", optional = false },
1919- { bin = cmd.iferr, msg = "required for `:GoIfErr`", optional = false },
1414+ { bin = cmd.gomodifytags, msg = "required for `:GoTagAdd`, `:GoTagRm`", optional = true },
1515+ { bin = cmd.impl, msg = "required for `:GoImpl`", optional = true },
1616+ { bin = cmd.iferr, msg = "required for `:GoIfErr`", optional = true },
2017 {
2118 bin = cmd.gotests,
2219 msg = "required for `:GoTestAdd`, `:GoTestsAll`, `:GoTestsExp`",
2323- optional = false,
2020+ optional = true,
2421 },
2525- { bin = cmd.dlv, msg = "required for debugging, (`nvim-dap`, `gopher.dap`)", optional = true },
2622 },
2723 treesitter = {
2828- { parser = "go", msg = "required for `gopher.nvim`", optional = false },
2424+ { parser = "go", msg = "required for `gopher.nvim`" },
2925 },
3026}
31272828+---@param module string
2929+---@return boolean
3030+local function is_lualib_found(module)
3131+ local is_found, _ = pcall(require, module)
3232+ return is_found
3333+end
3434+3535+---@param bin string
3636+---@return boolean
3737+local function is_binary_found(bin)
3838+ return vim.fn.executable(bin) == 1
3939+end
4040+4141+---@param ft string
4242+---@return boolean
4343+local function is_treesitter_parser_available(ft)
4444+ local ok, parser = pcall(vim.treesitter.get_parser, 0, ft)
4545+ return ok and parser ~= nil
4646+end
4747+3248function health.check()
3333- u.start "required plugins"
4949+ vim.health.start "required plugins"
3450 for _, plugin in ipairs(deps.plugin) do
3535- if u.is_lualib_found(plugin.lib) then
3636- u.ok(plugin.lib .. " installed")
5151+ if is_lualib_found(plugin.lib) then
5252+ vim.health.ok(plugin.lib .. " installed")
3753 else
3838- if plugin.optional then
3939- u.warn(plugin.lib .. " not found, " .. plugin.msg)
4040- else
4141- u.error(plugin.lib .. " not found, " .. plugin.msg)
4242- end
5454+ vim.health.error(plugin.lib .. " not found, " .. plugin.msg)
4355 end
4456 end
45574646- u.start "required binaries"
4747- u.info "all those binaries can be installed by `:GoInstallDeps`"
5858+ vim.health.start "required binaries"
5959+ vim.health.info "all those binaries can be installed by `:GoInstallDeps`"
4860 for _, bin in ipairs(deps.bin) do
4949- if u.is_binary_found(bin.bin) then
5050- u.ok(bin.bin .. " installed")
6161+ if is_binary_found(bin.bin) then
6262+ vim.health.ok(bin.bin .. " installed")
5163 else
5264 if bin.optional then
5353- u.warn(bin.bin .. " not found, " .. bin.msg)
6565+ vim.health.warn(bin.bin .. " not found, " .. bin.msg)
5466 else
5555- u.error(bin.bin .. " not found, " .. bin.msg)
6767+ vim.health.error(bin.bin .. " not found, " .. bin.msg)
5668 end
5769 end
5870 end
59716060- u.start "required treesitter parsers"
7272+ vim.health.start "required treesitter parsers"
6173 for _, parser in ipairs(deps.treesitter) do
6262- if u.is_treesitter_parser_available(parser.parser) then
6363- u.ok(parser.parser .. " parser installed")
7474+ if is_treesitter_parser_available(parser.parser) then
7575+ vim.health.ok(parser.parser .. " parser installed")
6476 else
6565- u.error(parser.parser .. " parser not found, " .. parser.msg)
7777+ vim.health.error(parser.parser .. " parser not found, " .. parser.msg)
6678 end
6779 end
6880end
+28-12
lua/gopher/iferr.lua
···11+-- Thanks https://github.com/koron/iferr for vim implementation
22+13---@toc_entry Iferr
24---@tag gopher.nvim-iferr
33----@text if you're using `iferr` tool, this module provides a way to automatically insert `if err != nil` check.
44----@usage execute `:GoIfErr` near any err variable to insert the check
55+---@text
66+--- `iferr` provides a way to way to automatically insert `if err != nil` check.
77+--- If you want to change `-message` option of `iferr` tool, see |gopher.nvim-config|
88+---
99+---@usage Execute `:GoIfErr` near any `err` variable to insert the check
510611local c = require "gopher.config"
1212+local u = require "gopher._utils"
1313+local r = require "gopher._utils.runner"
714local log = require "gopher._utils.log"
815local iferr = {}
9161010--- That's Lua implementation: github.com/koron/iferr
1117function iferr.iferr()
1212- local boff = vim.fn.wordcount().cursor_bytes
1818+ local curb = vim.fn.wordcount().cursor_bytes
1319 local pos = vim.fn.getcurpos()[2]
2020+ local fpath = vim.fn.expand "%"
14211515- local data = vim.fn.systemlist((c.commands.iferr .. " -pos " .. boff), vim.fn.bufnr "%")
1616- if vim.v.shell_error ~= 0 then
1717- if string.find(data[1], "no functions at") then
1818- vim.print "no function found"
1919- log.warn("iferr: no function at " .. boff)
2222+ local cmd = { c.commands.iferr, "-pos", curb }
2323+ if c.iferr.message ~= nil and type(c.iferr.message) == "string" then
2424+ table.insert(cmd, "-message")
2525+ table.insert(cmd, c.iferr.message)
2626+ end
2727+2828+ local rs = r.sync(cmd, {
2929+ stdin = u.readfile_joined(fpath),
3030+ })
3131+3232+ if rs.code ~= 0 then
3333+ if string.find(rs.stderr, "no functions at") then
3434+ u.notify("iferr: no function at " .. curb, vim.log.levels.ERROR)
3535+ log.warn("iferr: no function at " .. curb)
2036 return
2137 end
22382323- log.error("failed. output: " .. vim.inspect(data))
2424- error("iferr failed: " .. vim.inspect(data))
3939+ log.error("ferr: failed. output: " .. rs.stderr)
4040+ error("iferr failed: " .. rs.stderr)
2541 end
26422727- vim.fn.append(pos, data)
4343+ vim.fn.append(pos, u.remove_empty_lines(vim.split(rs.stdout, "\n")))
2844 vim.cmd [[silent normal! j=2j]]
2945 vim.fn.setpos(".", pos)
3046end
+40-66
lua/gopher/impl.lua
···11---@toc_entry Auto implementation of interface methods
22---@tag gopher.nvim-impl
33----@text impl is utilizing the `impl` tool to generate method stubs for interfaces.
44----@usage
55---- 1. put your coursor on the struct on which you want implement the interface
66---- and run `:GoImpl io.Reader`
77---- which will automatically choose the reciver for the methods and
88---- implement the `io.Reader` interface
99---- 2. same as previous but with custom receiver, so put your coursor on the struct
1010---- run `:GoImpl w io.Writer`
1111---- where `w` is the receiver and `io.Writer` is the interface
1212---- 3. specift receiver, struct, and interface
1313---- there's no need to put your coursor on the struct if you specify all arguments
1414---- `:GoImpl r RequestReader io.Reader`
1515---- where `r` is the receiver, `RequestReader` is the struct and `io.Reader` is the interface
33+---@text
44+--- Integration of `impl` tool to generate method stubs for interfaces.
55+---
66+---@usage 1. Automatically implement an interface for a struct:
77+--- - Place your cursor on the struct where you want to implement the interface.
88+--- - Run `:GoImpl io.Reader`
99+--- - This will automatically determine the receiver and implement the `io.Reader` interface.
1010+---
1111+--- 2. Specify a custom receiver:
1212+--- - Place your cursor on the struct
1313+--- - Run `:GoImpl w io.Writer`, where:
1414+--- - `w` is the receiver.
1515+--- - `io.Writer` is the interface to implement.
1616+---
1717+--- 3. Explicitly specify the receiver, struct, and interface:
1818+--- - No need to place the cursor on the struct if all arguments are provided.
1919+--- - Run `:GoImpl r RequestReader io.Reader`, where:
2020+--- - `r` is the receiver.
2121+--- - `RequestReader` is the struct.
2222+--- - `io.Reader` is the interface to implement.
1623---
1717---- simple example:
2424+--- Example:
1825--- >go
1926--- type BytesReader struct{}
2027--- // ^ put your cursor here
···2229---
2330--- // this is what you will get
2431--- func (b *BytesReader) Read(p []byte) (n int, err error) {
2525---- panic("not implemented") // TODO: Implement
3232+--- panic("not implemented") // TODO: Implement
2633--- }
2734--- <
2835···3239local u = require "gopher._utils"
3340local impl = {}
34413535----@return string
3636----@private
3737-local function get_struct()
3838- local ns = ts_utils.get_struct_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0)))
3939- if ns == nil then
4040- u.deferred_notify("put cursor on a struct or specify a receiver", vim.log.levels.INFO)
4141- return ""
4242- end
4343-4444- vim.api.nvim_win_set_cursor(0, {
4545- ns.dim.e.r,
4646- ns.dim.e.c,
4747- })
4848-4949- return ns.name
5050-end
5151-5242function impl.impl(...)
5343 local args = { ... }
5454- local iface, recv_name = "", ""
5555- local recv = get_struct()
4444+ local iface, recv = "", ""
4545+ local bufnr = vim.api.nvim_get_current_buf()
56465757- if #args == 0 then
5858- iface = vim.fn.input "impl: generating method stubs for interface: "
5959- vim.cmd "redraw!"
6060- if iface == "" then
6161- u.deferred_notify("usage: GoImpl f *File io.Reader", vim.log.levels.INFO)
6262- return
6363- end
6464- elseif #args == 1 then -- :GoImpl io.Reader
6565- recv = string.lower(recv) .. " *" .. recv
6666- vim.cmd "redraw!"
6767- iface = select(1, ...)
4747+ if #args == 1 then -- :GoImpl io.Reader
4848+ local st = ts_utils.get_struct_under_cursor(bufnr)
4949+ iface = args[1]
5050+ recv = string.lower(st.name) .. " *" .. st.name
6851 elseif #args == 2 then -- :GoImpl w io.Writer
6969- recv_name = select(1, ...)
7070- recv = string.format("%s *%s", recv_name, recv)
7171- iface = select(#args, ...)
7272- elseif #args > 2 then
7373- iface = select(#args, ...)
7474- recv = select(#args - 1, ...)
7575- recv_name = select(#args - 2, ...)
7676- recv = string.format("%s %s", recv_name, recv)
5252+ local st = ts_utils.get_struct_under_cursor(bufnr)
5353+ iface = args[2]
5454+ recv = args[1] .. " *" .. st.name
5555+ elseif #args == 3 then -- :GoImpl r Struct io.Reader
5656+ recv = args[1] .. " *" .. args[2]
5757+ iface = args[3]
7758 end
78597979- local output = r.sync(c.impl, {
8080- args = {
8181- "-dir",
8282- vim.fn.fnameescape(vim.fn.expand "%:p:h" --[[@as string]]),
8383- recv,
8484- iface,
8585- },
8686- on_exit = function(data, status)
8787- if not status == 0 then
8888- error("impl failed: " .. data)
8989- end
9090- end,
9191- })
6060+ local rs = r.sync { c.impl, "-dir", vim.fn.fnameescape(vim.fn.expand "%:p:h"), recv, iface }
6161+ if rs.code ~= 0 then
6262+ error("failed to implement interface: " .. rs.stderr)
6363+ end
92649365 local pos = vim.fn.getcurpos()[2]
6666+ local output = u.remove_empty_lines(vim.split(rs.stdout, "\n"))
6767+9468 table.insert(output, 1, "")
9569 vim.fn.append(pos, output)
9670end
+24-17
lua/gopher/init.lua
···11---- *gopher.nvim*
11+--- *gopher.nvim* Enhance your golang experience
22+---
33+--- MIT License Copyright (c) 2025 Oleksandr Smirnov
24---
35--- ==============================================================================
46---
57--- gopher.nvim is a minimalistic plugin for Go development in Neovim written in Lua.
68--- It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim.
77-99+---
810--- Table of Contents
99----@tag gopher.nvim-table-of-contents
1011---@toc
11121213local log = require "gopher._utils.log"
1314local tags = require "gopher.struct_tags"
1415local tests = require "gopher.gotests"
1515-local gocmd = require("gopher._utils.runner.gocmd").run
1616+local gocmd = require("gopher._utils.gocmd").run
1617local gopher = {}
17181819---@toc_entry Setup
1919----@tag gopher.nvim-setup
2020----@text Setup function. This method simply merges default configs with opts table.
2020+---@tag gopher.nvim-setup()
2121+---@text Setup function. This method simply merges default config with opts table.
2122--- You can read more about configuration at |gopher.nvim-config|
2222---- Calling this function is optional, if you ok with default settings. Look |gopher.nvim.config-defaults|
2323+--- Calling this function is optional, if you ok with default settings.
2424+--- See |gopher.nvim.config|
2325---
2424----@usage `require("gopher").setup {}` (replace `{}` with your `config` table)
2525----@param user_config gopher.Config
2626+---@usage >lua
2727+--- require("gopher").setup {} -- use default config or replace {} with your own
2828+--- <
2929+---@param user_config gopher.Config See |gopher.nvim-config|
2630gopher.setup = function(user_config)
2731 log.debug "setting up config"
2832 require("gopher.config").setup(user_config)
···3034end
31353236---@toc_entry Install dependencies
3333----@tag gopher.nvim-install-deps
3737+---@tag gopher.nvim-dependencies
3438---@text Gopher.nvim implements most of its features using third-party tools.
3535---- To install these tools, you can run `:GoInstallDeps` command
3636---- or call `require("gopher").install_deps()` if you want ues lua api.
3939+--- To install these tools, you can run `:GoInstallDeps` command
4040+--- or call `require("gopher").install_deps()` if you want to use lua api.
4141+--- By default dependencies will be installed asynchronously,
4242+--- to install them synchronously pass `{sync = true}` as an argument.
3743gopher.install_deps = require("gopher.installer").install_deps
38443945gopher.impl = require("gopher.impl").impl
4046gopher.iferr = require("gopher.iferr").iferr
4141-gopher.comment = require "gopher.comment"
4747+gopher.comment = require("gopher.comment").comment
42484349gopher.tags = {
4450 add = tags.add,
4551 rm = tags.remove,
5252+ clear = tags.clear,
4653}
47544855gopher.test = {
···5259}
53605461gopher.get = function(...)
5555- gocmd("get", { ... })
6262+ gocmd("get", ...)
5663end
57645865gopher.mod = function(...)
5959- gocmd("mod", { ... })
6666+ gocmd("mod", ...)
6067end
61686269gopher.generate = function(...)
6363- gocmd("generate", { ... })
7070+ gocmd("generate", ...)
6471end
65726673gopher.work = function(...)
6767- gocmd("work", { ... })
7474+ gocmd("work", ...)
6875end
69767077return gopher
+42-21
lua/gopher/installer.lua
···11local c = require("gopher.config").commands
22local r = require "gopher._utils.runner"
33local u = require "gopher._utils"
44+local log = require "gopher._utils.log"
45local installer = {}
5667local urls = {
77- gomodifytags = "github.com/fatih/gomodifytags",
88- impl = "github.com/josharian/impl",
99- gotests = "github.com/cweill/gotests/...",
1010- iferr = "github.com/koron/iferr",
1111- dlv = "github.com/go-delve/delve/cmd/dlv",
88+ gomodifytags = "github.com/fatih/gomodifytags@latest",
99+ impl = "github.com/josharian/impl@latest",
1010+ gotests = "github.com/cweill/gotests/...@develop",
1111+ iferr = "github.com/koron/iferr@latest",
1212}
13131414----@param pkg string
1515-local function install(pkg)
1616- local url = urls[pkg] .. "@latest"
1717- r.sync(c.go, {
1818- args = { "install", url },
1919- on_exit = function(data, status)
2020- if not status == 0 then
2121- error("go install failed: " .. data)
2222- return
2323- end
2424- u.notify("installed: " .. url)
2525- end,
2626- })
1414+---@param opt vim.SystemCompleted
1515+---@param url string
1616+local function handle_intall_exit(opt, url)
1717+ if opt.code ~= 0 then
1818+ vim.schedule(function()
1919+ u.notify("go install failed: " .. url)
2020+ end)
2121+2222+ log.error("go install failed:", "url", url, "opt", vim.inspect(opt))
2323+ return
2424+ end
2525+2626+ vim.schedule(function()
2727+ u.notify("go install-ed: " .. url)
2828+ end)
2929+end
3030+3131+---@param url string
3232+local function install(url)
3333+ r.async({ c.go, "install", url }, function(opt)
3434+ handle_intall_exit(opt, url)
3535+ end)
3636+end
3737+3838+---@param url string
3939+local function install_sync(url)
4040+ local rs = r.sync { c.go, "install", url }
4141+ handle_intall_exit(rs, url)
2742end
28432944---Install required go deps
3030-function installer.install_deps()
3131- for pkg, _ in pairs(urls) do
3232- install(pkg)
4545+---@param opts? {sync:boolean}
4646+function installer.install_deps(opts)
4747+ opts = opts or {}
4848+ for _, url in pairs(urls) do
4949+ if opts.sync then
5050+ install_sync(url)
5151+ else
5252+ install(url)
5353+ end
3354 end
3455end
3556
+83-73
lua/gopher/struct_tags.lua
···11----@toc_entry Modifty struct tags
11+---@toc_entry Modify struct tags
22---@tag gopher.nvim-struct-tags
33----@text struct-tags is utilizing the `gomodifytags` tool to add or remove tags to struct fields.
44----@usage - put your coursor on the struct
55---- - run `:GoTagAdd json` to add json tags to struct fields
66---- - run `:GoTagRm json` to remove json tags to struct fields
33+---@text
44+--- `struct_tags` is utilizing the `gomodifytags` tool to add or remove tags to struct fields.
75---
88---- note: if you dont spesify the tag it will use `json` as default
66+---@usage
77+--- How to add/remove tags to struct fields:
88+--- 1. Place cursor on the struct
99+--- 2. Run `:GoTagAdd json` to add json tags to struct fields
1010+--- 3. Run `:GoTagRm json` to remove json tags to struct fields
911---
1010---- simple example:
1212+--- To clear all tags from struct run: `:GoTagClear`
1313+---
1414+--- NOTE: if you dont specify the tag it will use `json` as default
1515+---
1616+--- Example:
1117--- >go
1218--- // before
1319--- type User struct {
···2430--- }
2531--- <
26322727-local ts_utils = require "gopher._utils.ts"
3333+local ts = require "gopher._utils.ts"
2834local r = require "gopher._utils.runner"
2935local c = require "gopher.config"
3636+local u = require "gopher._utils"
3737+local log = require "gopher._utils.log"
3038local struct_tags = {}
31393232-local function modify(...)
3333- local fpath = vim.fn.expand "%" ---@diagnostic disable-line: missing-parameter
3434- local ns = ts_utils.get_struct_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0)))
3535- if ns == nil then
3636- return
3737- end
4040+---@param fpath string
4141+---@param bufnr integer
4242+---@param user_args string[]
4343+---@dochide
4444+local function handle_tags(fpath, bufnr, user_args)
4545+ local st = ts.get_struct_under_cursor(bufnr)
38463947 -- stylua: ignore
4040- local cmd_args = {
4848+ local cmd = {
4949+ c.commands.gomodifytags,
4150 "-transform", c.gotag.transform,
4251 "-format", "json",
4352 "-file", fpath,
4444- "-w"
5353+ "-w",
4554 }
46554747- -- by struct name of line pos
4848- if ns.name == nil then
4949- local _, csrow, _, _ = unpack(vim.fn.getpos ".")
5050- table.insert(cmd_args, "-line")
5151- table.insert(cmd_args, csrow)
5656+ if st.is_varstruct then
5757+ table.insert(cmd, "-line")
5858+ table.insert(cmd, string.format("%d,%d", st.start_line, st.end_line))
5259 else
5353- table.insert(cmd_args, "-struct")
5454- table.insert(cmd_args, ns.name)
6060+ table.insert(cmd, "-struct")
6161+ table.insert(cmd, st.name)
5562 end
56635757- -- set user args for cmd
5858- local arg = { ... }
5959- for _, v in ipairs(arg) do
6060- table.insert(cmd_args, v)
6464+ for _, v in ipairs(user_args) do
6565+ table.insert(cmd, v)
6166 end
62676363- -- set default tag for "clear tags"
6464- if #arg == 1 and arg[1] ~= "-clear-tags" then
6565- table.insert(cmd_args, "json")
6868+ local rs = r.sync(cmd)
6969+ if rs.code ~= 0 then
7070+ log.error("tags: failed to set tags " .. rs.stderr)
7171+ error("failed to set tags " .. rs.stderr)
6672 end
67736868- local output = r.sync(c.commands.gomodifytags, {
6969- args = cmd_args,
7070- on_exit = function(data, status)
7171- if not status == 0 then
7272- error("gotag failed: " .. data)
7373- end
7474- end,
7575- })
7474+ local res = vim.json.decode(rs.stdout)
7575+ if res["errors"] then
7676+ log.error("tags: got an error " .. vim.inspect(res))
7777+ error("failed to set tags " .. vim.inspect(res["errors"]))
7878+ end
76797777- -- decode goted value
7878- local tagged = vim.json.decode(table.concat(output))
7979- if
8080- tagged.errors ~= nil
8181- or tagged.lines == nil
8282- or tagged["start"] == nil
8383- or tagged["start"] == 0
8484- then
8585- error("failed to set tags " .. vim.inspect(tagged))
8080+ for i, v in ipairs(res["lines"]) do
8181+ res["lines"][i] = u.trimend(v)
8682 end
87838884 vim.api.nvim_buf_set_lines(
8989- 0,
9090- tagged.start - 1,
9191- tagged.start - 1 + #tagged.lines,
9292- false,
9393- tagged.lines
8585+ bufnr,
8686+ res["start"] - 1,
8787+ res["start"] - 1 + #res["lines"],
8888+ true,
8989+ res["lines"]
9490 )
9595- vim.cmd "write"
9691end
97929898--- add tags to struct under cursor
9999-function struct_tags.add(...)
100100- local arg = { ... }
101101- if #arg == nil or arg == "" then
102102- arg = { "json" }
9393+---@param args string[]
9494+---@return string
9595+---@dochide
9696+local function handler_user_args(args)
9797+ if #args == 0 then
9898+ return c.gotag.default_tag
10399 end
100100+ return table.concat(args, ",")
101101+end
104102105105- local cmd_args = { "-add-tags" }
106106- for _, v in ipairs(arg) do
107107- table.insert(cmd_args, v)
108108- end
103103+-- Adds tags to a struct under the cursor
104104+-- See |gopher.nvim-struct-tags|
105105+---@param ... string Tags to add to the struct fields. If not provided, it will use [config.gotag.default_tag]
106106+---@dochide
107107+function struct_tags.add(...)
108108+ local args = { ... }
109109+ local fpath = vim.fn.expand "%"
110110+ local bufnr = vim.api.nvim_get_current_buf()
109111110110- modify(unpack(cmd_args))
112112+ local user_tags = handler_user_args(args)
113113+ handle_tags(fpath, bufnr, { "-add-tags", user_tags })
111114end
112115113113--- remove tags to struct under cursor
116116+-- Removes tags from a struct under the cursor
117117+-- See `:h gopher.nvim-struct-tags`
118118+---@dochide
119119+---@param ... string Tags to add to the struct fields. If not provided, it will use [config.gotag.default_tag]
114120function struct_tags.remove(...)
115115- local arg = { ... }
116116- if #arg == nil or arg == "" then
117117- arg = { "json" }
118118- end
121121+ local args = { ... }
122122+ local fpath = vim.fn.expand "%"
123123+ local bufnr = vim.api.nvim_get_current_buf()
119124120120- local cmd_args = { "-remove-tags" }
121121- for _, v in ipairs(arg) do
122122- table.insert(cmd_args, v)
123123- end
125125+ local user_tags = handler_user_args(args)
126126+ handle_tags(fpath, bufnr, { "-remove-tags", user_tags })
127127+end
124128125125- modify(unpack(cmd_args))
129129+-- Removes all tags from a struct under the cursor
130130+-- See `:h gopher.nvim-struct-tags`
131131+---@dochide
132132+function struct_tags.clear()
133133+ local fpath = vim.fn.expand "%"
134134+ local bufnr = vim.api.nvim_get_current_buf()
135135+ handle_tags(fpath, bufnr, { "-clear-tags" })
126136end
127137128138return struct_tags
···11+package main
22+33+type Test struct {
44+ ID int
55+ Name string
66+ Num int64
77+ Another struct {
88+ First int
99+ Second string
1010+ }
1111+}
+11
spec/fixtures/tags/add_many_output.go
···11+package main
22+33+type Test struct {
44+ ID int `test4:"id" test5:"id" test1:"id" test2:"id"`
55+ Name string `test4:"name" test5:"name" test1:"name" test2:"name"`
66+ Num int64 `test4:"num" test5:"num" test1:"num" test2:"num"`
77+ Another struct {
88+ First int `test4:"first" test5:"first" test1:"first" test2:"first"`
99+ Second string `test4:"second" test5:"second" test1:"second" test2:"second"`
1010+ } `test4:"another" test5:"another" test1:"another" test2:"another"`
1111+}
+11
spec/fixtures/tags/clear_input.go
···11+package main
22+33+type Test struct {
44+ ID int `json:"id" yaml:"id" xml:"id" db:"id"`
55+ Name string `json:"name" yaml:"name" xml:"name" db:"name"`
66+ Num int64 `json:"num" yaml:"num" xml:"num" db:"num"`
77+ Another struct {
88+ First int `json:"first" yaml:"first" xml:"first" db:"first"`
99+ Second string `json:"second" yaml:"second" xml:"second" db:"second"`
1010+ } `json:"another" yaml:"another" xml:"another" db:"another"`
1111+}
+11
spec/fixtures/tags/clear_output.go
···11+package main
22+33+type Test struct {
44+ ID int
55+ Name string
66+ Num int64
77+ Another struct {
88+ First int
99+ Second string
1010+ }
1111+}
+18
spec/fixtures/tags/many_input.go
···11+package main
22+33+type (
44+ TestOne struct {
55+ Asdf string
66+ ID int
77+ }
88+99+ TestTwo struct {
1010+ Fesa int
1111+ A bool
1212+ }
1313+1414+ TestThree struct {
1515+ Asufj int
1616+ Fs string
1717+ }
1818+)
+18
spec/fixtures/tags/many_output.go
···11+package main
22+33+type (
44+ TestOne struct {
55+ Asdf string
66+ ID int
77+ }
88+99+ TestTwo struct {
1010+ Fesa int `testing:"fesa"`
1111+ A bool `testing:"a"`
1212+ }
1313+1414+ TestThree struct {
1515+ Asufj int
1616+ Fs string
1717+ }
1818+)
+11
spec/fixtures/tags/svar_input.go
···11+package main
22+33+func main() {
44+ s := struct {
55+ API string
66+ Key string
77+ }{
88+ API: "api.com",
99+ Key: "key",
1010+ }
1111+}
+11
spec/fixtures/tags/svar_output.go
···11+package main
22+33+func main() {
44+ s := struct {
55+ API string `xml:"api"`
66+ Key string `xml:"key"`
77+ }{
88+ API: "api.com",
99+ Key: "key",
1010+ }
1111+}
+8
spec/fixtures/tags/var_input.go
···11+package main
22+33+func main() {
44+ var a struct {
55+ TestField1 string
66+ TestField2 string
77+ }
88+}
+8
spec/fixtures/tags/var_output.go
···11+package main
22+33+func main() {
44+ var a struct {
55+ TestField1 string `yaml:"test_field_1"`
66+ TestField2 string `yaml:"test_field_2"`
77+ }
88+}
+5
spec/fixtures/tests/function_input.go
···11+package fortest
22+33+func Add(x, y int) int {
44+ return 2 + x + y
55+}
+24
spec/fixtures/tests/function_output.go
···11+package fortest
22+33+import "testing"
44+55+func TestAdd(t *testing.T) {
66+ type args struct {
77+ x int
88+ y int
99+ }
1010+ tests := []struct {
1111+ name string
1212+ args args
1313+ want int
1414+ }{
1515+ // TODO: Add test cases.
1616+ }
1717+ for _, tt := range tests {
1818+ t.Run(tt.name, func(t *testing.T) {
1919+ if got := Add(tt.args.x, tt.args.y); got != tt.want {
2020+ t.Errorf("Add() = %v, want %v", got, tt.want)
2121+ }
2222+ })
2323+ }
2424+}
+7
spec/fixtures/tests/method_input.go
···11+package fortest
22+33+type ForTest struct{}
44+55+func (t *ForTest) Add(x, y int) int {
66+ return 2 + x + y
77+}
+26
spec/fixtures/tests/method_output.go
···11+package fortest
22+33+import "testing"
44+55+func TestForTest_Add(t *testing.T) {
66+ type args struct {
77+ x int
88+ y int
99+ }
1010+ tests := []struct {
1111+ name string
1212+ tr *ForTest
1313+ args args
1414+ want int
1515+ }{
1616+ // TODO: Add test cases.
1717+ }
1818+ for _, tt := range tests {
1919+ t.Run(tt.name, func(t *testing.T) {
2020+ tr := &ForTest{}
2121+ if got := tr.Add(tt.args.x, tt.args.y); got != tt.want {
2222+ t.Errorf("ForTest.Add() = %v, want %v", got, tt.want)
2323+ }
2424+ })
2525+ }
2626+}
···11+local t = require "spec.testutils"
22+local child, T = t.setup "gotests"
33+44+--- NOTE: :GoTestAdd is the only place that has actual logic
55+--- All other parts are handled `gotests` itself.
66+77+---@param fpath string
88+---@return string
99+local function read_testfile(fpath)
1010+ return t.readfile(fpath:gsub(".go", "_test.go"))
1111+end
1212+1313+T["gotests"]["should add test for function under cursor"] = function()
1414+ local rs = t.setup_test("tests/function", child, { 3, 5 })
1515+ child.cmd "GoTestAdd"
1616+1717+ t.eq(rs.fixtures.output, read_testfile(rs.tmp))
1818+ t.cleanup(rs)
1919+end
2020+2121+T["gotests"]["should add test for method under cursor"] = function()
2222+ local rs = t.setup_test("tests/method", child, { 5, 19 })
2323+ child.cmd "GoTestAdd"
2424+2525+ t.eq(rs.fixtures.output, read_testfile(rs.tmp))
2626+ t.cleanup(rs)
2727+end
2828+2929+return T