Merge pull request #106 from olexsmir/develop

This commit is contained in:
Smirnov Oleksandr 2025-03-23 23:08:09 +02:00 committed by GitHub
commit 847c79ab76
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
81 changed files with 1588 additions and 1072 deletions

4
.envrc Normal file
View file

@ -0,0 +1,4 @@
dotenv
# GOPHER_DIR - needed only for tests, to find the root of the project
env_vars_required GOPHER_DIR

View file

@ -1,12 +1,18 @@
name: linters name: linters
on: [push, pull_request]
on:
push:
branches:
- main
- develop
pull_request:
jobs: jobs:
linters: linters:
name: linters name: Lua
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: JohnnyMorganz/stylua-action@v3 - uses: JohnnyMorganz/stylua-action@v3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@ -17,3 +23,36 @@ jobs:
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
args: . args: .
docs:
name: Docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Task
uses: arduino/setup-task@v1
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install NeoVim
uses: rhysd/action-setup-vim@v1
with:
neovim: true
version: stable
- name: Cache .tests
uses: actions/cache@v4
with:
path: |
${{ github.workspace }}/.tests
key: ${{ runner.os }}-tests-${{ hashFiles('${{ github.workspace }}/.tests') }}
- name: Generate docs
run: task docgen
- name: Diff
run: |
git diff doc
exit $(git status --porcelain doc | wc -l | tr -d " ")

View file

@ -1,25 +1,20 @@
name: tests name: tests
on: [push, pull_request]
on:
push:
branches:
- main
- develop
pull_request:
jobs: jobs:
tests: tests:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest] os: [ubuntu-latest]
nvim_version: version:
- stable
- nightly - nightly
- v0.7.0
- v0.7.2
- v0.8.0
- v0.8.1
- v0.8.2
- v0.8.3
- v0.9.0
- v0.9.1
- v0.9.2
- v0.9.4
- v0.9.5
- v0.10.0
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Install Task - name: Install Task
@ -28,18 +23,38 @@ jobs:
version: 3.x version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@v3 - name: Install Go
uses: actions/setup-go@v5
with:
go-version: "1.24.0"
check-latest: false
- name: Install Neovim - name: Install NeoVim
uses: rhysd/action-setup-vim@v1
with:
neovim: true
version: ${{ matrix.version }}
- uses: actions/checkout@v4
- name: Cache .tests
uses: actions/cache@v4
with:
path: |
${{ github.workspace }}/.tests
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-tests-${{ hashFiles('${{ github.workspace }}/.tests') }}
- name: Install Go bins
run: | run: |
mkdir -p /tmp/nvim # TODO: install with :GoInstallDeps
wget -q https://github.com/neovim/neovim/releases/download/${{ matrix.nvim_version }}/nvim.appimage -O /tmp/nvim/nvim.appimage go install github.com/fatih/gomodifytags@latest
cd /tmp/nvim go install github.com/josharian/impl@latest
chmod a+x ./nvim.appimage go install github.com/cweill/gotests/...@latest
./nvim.appimage --appimage-extract go install github.com/koron/iferr@latest
echo "/tmp/nvim/squashfs-root/usr/bin/" >> $GITHUB_PATH
- name: Run Tests - name: Run Tests
run: | run: |
nvim --version nvim --version
task test task tests

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
/playground/ /playground/
/.tests/ /.tests/
/.env

View file

@ -1,10 +0,0 @@
{
"diagnostics.globals": [
"describe",
"it",
"before_each",
"after_each",
"before_all",
"after_all"
]
}

View file

@ -18,14 +18,12 @@ You can install these with:
```bash ```bash
sudo pacman -S selene stylua sudo pacman -S selene stylua
# or whatever is your package manager # or whatever is your package manager
# or way of installing pkgs
``` ```
For formatting use this following commands, or setup your editor to integrate with selene/stylua: For formatting use this following commands, or setup your editor to integrate with selene/stylua:
```bash ```bash
task format task stylua
task format:check # will check if your code formatted task lint # lintering and format chewing
task lint
``` ```
### Documentation ### Documentation
@ -39,17 +37,15 @@ task docgen
``` ```
### Commit messages ### Commit messages
We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), please follow it. We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), please follow it.
### Testing ### Testing
For testing this plugins uses [plenary.nvim](https://github.com/nvim-lua/plenary.nvim). For testing this plugins uses [mini.test](https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-test.md).
All tests live in [/spec](https://github.com/olexsmir/gopher.nvim/tree/main/spec) dir. All tests live in [/spec](./spec) dir.
You can run tests with: You can run tests with:
```bash ```bash
task test
# also there are some aliases for that
task tests task tests
task spec
``` ```

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Oleksandr Smirnov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -10,21 +10,19 @@ It's **NOT** an LSP tool, the main goal of this plugin is to add go tooling supp
## Install (using [lazy.nvim](https://github.com/folke/lazy.nvim)) ## Install (using [lazy.nvim](https://github.com/folke/lazy.nvim))
Pre-dependency: Requirements:
- [Go](https://github.com/golang/go) - **Neovim 0.10** or later
- `go` treesitter parser, install by `:TSInstall go` - Treesitter `go` parser(`:TSInstall go`)
- [Go](https://github.com/golang/go) installed (tested on 1.23)
```lua ```lua
{ {
"olexsmir/gopher.nvim", "olexsmir/gopher.nvim",
ft = "go", ft = "go",
-- branch = "develop", -- if you want develop branch -- branch = "develop"
-- keep in mind, it might break everything
dependencies = { dependencies = {
"nvim-lua/plenary.nvim",
"nvim-treesitter/nvim-treesitter", "nvim-treesitter/nvim-treesitter",
"mfussenegger/nvim-dap", -- (optional) only if you use `gopher.dap`
}, },
-- (optional) will update plugin's deps on every update -- (optional) will update plugin's deps on every update
build = function() build = function()
@ -35,23 +33,28 @@ Pre-dependency:
} }
``` ```
## Configuratoin ## Configuration
> [!IMPORTANT] > [!IMPORTANT]
> >
> If you need more info look `:h gopher.nvim` > If you need more info look `:h gopher.nvim`
**Take a look at default options** **Take a look at default options (might be a bit outdated, look `:h gopher.nvim-config`)**
```lua ```lua
require("gopher").setup { require("gopher").setup {
-- log level, you might consider using DEBUG or TRACE for debugging the plugin
log_level = vim.log.levels.INFO,
-- timeout for running internal commands
timeout = 2000,
commands = { commands = {
go = "go", go = "go",
gomodifytags = "gomodifytags", gomodifytags = "gomodifytags",
gotests = "gotests", gotests = "gotests",
impl = "impl", impl = "impl",
iferr = "iferr", iferr = "iferr",
dlv = "dlv",
}, },
gotests = { gotests = {
-- gotests doesn't have template named "default" so this plugin uses "default" to set the default template -- gotests doesn't have template named "default" so this plugin uses "default" to set the default template
@ -59,11 +62,16 @@ require("gopher").setup {
-- path to a directory containing custom test code templates -- path to a directory containing custom test code templates
template_dir = nil, template_dir = nil,
-- switch table tests from using slice to map (with test name for the key) -- switch table tests from using slice to map (with test name for the key)
-- works only with gotests installed from develop branch
named = false, named = false,
}, },
gotag = { gotag = {
transform = "snakecase", transform = "snakecase",
-- default tags to add to struct fields
default_tag = "json",
},
iferr = {
-- choose a custom error message
message = nil,
}, },
} }
``` ```
@ -87,7 +95,6 @@ require("gopher").setup {
- [impl](https://github.com/josharian/impl) - [impl](https://github.com/josharian/impl)
- [gotests](https://github.com/cweill/gotests) - [gotests](https://github.com/cweill/gotests)
- [iferr](https://github.com/koron/iferr) - [iferr](https://github.com/koron/iferr)
- [dlv](github.com/go-delve/delve/cmd/dlv)
</details> </details>
<details> <details>
@ -215,20 +222,6 @@ require("gopher").setup {
``` ```
</details> </details>
<details>
<summary>
<b>Setup <a href="https://github.com/mfussenegger/nvim-dap">nvim-dap</a> for go in one line</b>
</summary>
THIS FEATURE WILL BE REMOVED IN `0.1.6`
note [nvim-dap](https://github.com/mfussenegger/nvim-dap) has to be installed
```lua
require("gopher.dap").setup()
```
</details>
## Contributing ## Contributing
PRs are always welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) PRs are always welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md)
@ -236,5 +229,4 @@ PRs are always welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md)
## Thanks ## Thanks
- [go.nvim](https://github.com/ray-x/go.nvim) - [go.nvim](https://github.com/ray-x/go.nvim)
- [nvim-dap-go](https://github.com/leoluz/nvim-dap-go)
- [iferr](https://github.com/koron/iferr) - [iferr](https://github.com/koron/iferr)

View file

@ -1,49 +1,34 @@
version: "3" version: "3"
tasks: tasks:
format:
desc: formats all lua files in repo
cmds:
- stylua .
lint: lint:
desc: runs all linters desc: runs all linters
cmds: cmds:
- task: selene - task: selene
- task: stylua:check - stylua --check .
selene: selene:
desc: runs lua linter(selene) desc: runs lua linter(selene)
cmds: cmds:
- selene . - selene .
stylua:check:
desc: runs stylua in check mode
cmds:
- stylua --check .
stylua: stylua:
desc: runs lua formatter desc: runs lua formatter
cmds: cmds:
- stylua . - stylua .
test: tests:
desc: runs all tests desc: run all tests
aliases: [tests, spec]
cmds: cmds:
- | - |
nvim --headless \ nvim --clean --headless \
-u ./scripts/minimal_init.lua \ -u ./scripts/minimal_init.lua \
-c "PlenaryBustedDirectory spec \ -c "lua MiniTest.run()"
{minimal_init='./scripts/minimal_init.lua' \
,sequential=true}" \
-c ":qa!"
docgen: docgen:
desc: generate vimhelp desc: generate vimhelp
cmds: cmds:
- | - |
nvim --noplugin \ nvim --clean --headless \
--headless \
-u "./scripts/minimal_init.lua" \ -u "./scripts/minimal_init.lua" \
-c "luafile ./scripts/docgen.lua" \ -c "luafile ./scripts/docgen.lua" \
-c ":qa!" -c ":qa!"

View file

@ -1,3 +0,0 @@
function! health#gopher#check()
lua require("gopher.health").check()
endfunction

View file

@ -1,61 +1,62 @@
*gopher.nvim* *gopher.nvim* Enhance your golang experience
MIT License Copyright (c) 2025 Oleksandr Smirnov
============================================================================== ==============================================================================
gopher.nvim is a minimalistic plugin for Go development in Neovim written in Lua. gopher.nvim is a minimalistic plugin for Go development in Neovim written in Lua.
It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim. It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim.
------------------------------------------------------------------------------
*gopher.nvim-table-of-contents*
Table of Contents Table of Contents
Setup....................................................|gopher.nvim-setup| Setup ................................................ |gopher.nvim-setup()|
Install dependencies..............................|gopher.nvim-install-deps| Install dependencies ............................ |gopher.nvim-dependencies|
Configuration...........................................|gopher.nvim-config| Config ................................................ |gopher.nvim-config|
Modifty struct tags................................|gopher.nvim-struct-tags| Commands ............................................ |gopher.nvim-commands|
Auto implementation of interface methods..................|gopher.nvim-impl| Modify struct tags ............................... |gopher.nvim-struct-tags|
Generating unit tests boilerplate......................|gopher.nvim-gotests| Auto implementation of interface methods ................ |gopher.nvim-impl|
Iferr....................................................|gopher.nvim-iferr| Generating unit tests boilerplate .................... |gopher.nvim-gotests|
Generate comments.....................................|gopher.nvim-comments| Iferr .................................................. |gopher.nvim-iferr|
Setup `nvim-dap` for Go......................................|gopher.nvim-dap| Generate comments ................................... |gopher.nvim-comments|
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
*gopher.nvim-setup* *gopher.nvim-setup()*
`gopher.setup`({user_config}) `gopher.setup`({user_config})
Setup function. This method simply merges default configs with opts table. Setup function. This method simply merges default config with opts table.
You can read more about configuration at |gopher.nvim-config| You can read more about configuration at |gopher.nvim-config|
Calling this function is optional, if you ok with default settings. Look |gopher.nvim.config-defaults| Calling this function is optional, if you ok with default settings.
See |gopher.nvim.config|
Usage ~ Usage ~
`require("gopher").setup {}` (replace `{}` with your `config` table) >lua
require("gopher").setup {} -- use default config or replace {} with your own
<
Parameters ~ Parameters ~
{user_config} gopher.Config {user_config} `(gopher.Config)` See |gopher.nvim-config|
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
*gopher.nvim-install-deps* *gopher.nvim-dependencies*
`gopher.install_deps` `gopher.install_deps`
Gopher.nvim implements most of its features using third-party tools. Gopher.nvim implements most of its features using third-party tools.
To install these tools, you can run `:GoInstallDeps` command To install these tools, you can run `:GoInstallDeps` command
or call `require("gopher").install_deps()` if you want ues lua api. or call `require("gopher").install_deps()` if you want to use lua api.
By default dependencies will be installed asynchronously,
to install them synchronously pass `{sync = true}` as an argument.
============================================================================== ==============================================================================
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
*gopher.nvim-config* *gopher.nvim-config*
config it is the place where you can configure the plugin.
also this is optional is you're ok with default settings.
You can look at default options |gopher.nvim-config-defaults|
------------------------------------------------------------------------------
*gopher.nvim-config-defaults*
`default_config` `default_config`
>lua >lua
local default_config = { local default_config = {
--minidoc_replace_end -- log level, you might consider using DEBUG or TRACE for debugging the plugin
-- log level, you might consider using DEBUG or TRACE for degugging the plugin
---@type number ---@type number
log_level = vim.log.levels.INFO, log_level = vim.log.levels.INFO,
-- timeout for running internal commands
---@type number
timeout = 2000,
-- user specified paths to binaries -- user specified paths to binaries
---@class gopher.ConfigCommand ---@class gopher.ConfigCommand
commands = { commands = {
@ -64,7 +65,6 @@ You can look at default options |gopher.nvim-config-defaults|
gotests = "gotests", gotests = "gotests",
impl = "impl", impl = "impl",
iferr = "iferr", iferr = "iferr",
dlv = "dlv",
}, },
---@class gopher.ConfigGotests ---@class gopher.ConfigGotests
gotests = { gotests = {
@ -74,13 +74,20 @@ You can look at default options |gopher.nvim-config-defaults|
---@type string|nil ---@type string|nil
template_dir = nil, template_dir = nil,
-- switch table tests from using slice to map (with test name for the key) -- switch table tests from using slice to map (with test name for the key)
-- works only with gotests installed from develop branch
named = false, named = false,
}, },
---@class gopher.ConfigGoTag ---@class gopher.ConfigGoTag
gotag = { gotag = {
---@type gopher.ConfigGoTagTransform ---@type gopher.ConfigGoTagTransform
transform = "snakecase", transform = "snakecase",
-- default tags to add to struct fields
default_tag = "json",
},
iferr = {
-- choose a custom error message
---@type string|nil
message = nil,
}, },
} }
< <
@ -88,18 +95,32 @@ Class ~
{gopher.Config} {gopher.Config}
==============================================================================
------------------------------------------------------------------------------
*gopher.nvim-commands*
If don't want to automatically register plugins' commands,
you can set `vim.g.gopher_register_commands` to `false`, before loading the plugin.
============================================================================== ==============================================================================
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
*gopher.nvim-struct-tags* *gopher.nvim-struct-tags*
struct-tags is utilizing the `gomodifytags` tool to add or remove tags to struct fields.
`struct_tags` is utilizing the `gomodifytags` tool to add or remove tags to struct fields.
Usage ~ Usage ~
- put your coursor on the struct
- run `:GoTagAdd json` to add json tags to struct fields
- run `:GoTagRm json` to remove json tags to struct fields
note: if you dont spesify the tag it will use `json` as default How to add/remove tags to struct fields:
1. Place cursor on the struct
2. Run `:GoTagAdd json` to add json tags to struct fields
3. Run `:GoTagRm json` to remove json tags to struct fields
simple example: To clear all tags from struct run: `:GoTagClear`
NOTE: if you dont specify the tag it will use `json` as default
Example:
>go >go
// before // before
type User struct { type User struct {
@ -116,26 +137,32 @@ simple example:
} }
< <
============================================================================== ==============================================================================
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
*gopher.nvim-impl* *gopher.nvim-impl*
impl is utilizing the `impl` tool to generate method stubs for interfaces.
Integration of `impl` tool to generate method stubs for interfaces.
Usage ~ Usage ~
1. Automatically implement an interface for a struct:
- Place your cursor on the struct where you want to implement the interface.
- Run `:GoImpl io.Reader`
- This will automatically determine the receiver and implement the `io.Reader` interface.
1. put your coursor on the struct on which you want implement the interface 2. Specify a custom receiver:
and run `:GoImpl io.Reader` - Place your cursor on the struct
which will automatically choose the reciver for the methods and - Run `:GoImpl w io.Writer`, where:
implement the `io.Reader` interface - `w` is the receiver.
2. same as previous but with custom receiver, so put your coursor on the struct - `io.Writer` is the interface to implement.
run `:GoImpl w io.Writer`
where `w` is the receiver and `io.Writer` is the interface
3. specift receiver, struct, and interface
there's no need to put your coursor on the struct if you specify all arguments
`:GoImpl r RequestReader io.Reader`
where `r` is the receiver, `RequestReader` is the struct and `io.Reader` is the interface
simple example: 3. Explicitly specify the receiver, struct, and interface:
- No need to place the cursor on the struct if all arguments are provided.
- Run `:GoImpl r RequestReader io.Reader`, where:
- `r` is the receiver.
- `RequestReader` is the struct.
- `io.Reader` is the interface to implement.
Example:
>go >go
type BytesReader struct{} type BytesReader struct{}
// ^ put your cursor here // ^ put your cursor here
@ -143,78 +170,51 @@ simple example:
// this is what you will get // this is what you will get
func (b *BytesReader) Read(p []byte) (n int, err error) { func (b *BytesReader) Read(p []byte) (n int, err error) {
panic("not implemented") // TODO: Implement panic("not implemented") // TODO: Implement
} }
< <
============================================================================== ==============================================================================
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
*gopher.nvim-gotests* *gopher.nvim-gotests*
gotests is utilizing the `gotests` tool to generate unit tests boilerplate. gotests is utilizing the `gotests` tool to generate unit tests boilerplate.
Usage ~ Usage ~
- generate unit test for spesisfic function/method - Generate unit test for specific function/method:
- to specift the function/method put your cursor on it 1. Place your cursor on the desired function/method.
- run `:GoTestAdd` 2. Run `:GoTestAdd`
- generate unit tests for all functions/methods in current file - Generate unit tests for *all* functions/methods in current file:
- run `:GoTestsAll` - run `:GoTestsAll`
- generate unit tests only for exported(public) functions/methods - Generate unit tests *only* for *exported(public)* functions/methods:
- run `:GoTestsExp` - run `:GoTestsExp`
you can also specify the template to use for generating the tests. see |gopher.nvim-config| You can also specify the template to use for generating the tests. See |gopher.nvim-config|
more details about templates can be found at: https://github.com/cweill/gotests More details about templates can be found at: https://github.com/cweill/gotests
If you prefer named tests, you can enable them in |gopher.nvim-config|.
------------------------------------------------------------------------------
*gopher.nvim-gotests-named*
if you prefare using named tests, you can enable it in the config.
but you would need to install `gotests@develop` because stable version doesn't support this feature.
you can do it with:
>lua
-- simply run go get in your shell:
go install github.com/cweill/gotests/...@develop
-- if you want to install it within neovim, you can use one of this:
vim.fn.jobstart("go install github.com/cweill/gotests/...@develop")
-- or if you want to use mason:
require("mason-tool-installer").setup {
ensure_installed = {
{ "gotests", version = "develop" },
}
}
<
if you choose to install `gotests` within neovim, i recommend adding it to your `build` section in your |lazy.nvim|
============================================================================== ==============================================================================
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
*gopher.nvim-iferr* *gopher.nvim-iferr*
if you're using `iferr` tool, this module provides a way to automatically insert `if err != nil` check.
`iferr` provides a way to way to automatically insert `if err != nil` check.
If you want to change `-message` option of `iferr` tool, see |gopher.nvim-config|
Usage ~ Usage ~
execute `:GoIfErr` near any err variable to insert the check Execute `:GoIfErr` near any `err` variable to insert the check
============================================================================== ==============================================================================
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
*gopher.nvim-comments* *gopher.nvim-comments*
Usage ~
Execute `:GoCmt` to generate a comment for the current function/method/struct/etc on this line.
This module provides a way to generate comments for Go code. This module provides a way to generate comments for Go code.
==============================================================================
------------------------------------------------------------------------------
*gopher.nvim-dap*
This module sets up `nvim-dap` for Go.
Usage ~ Usage ~
just call `require("gopher.dap").setup()`, and you're good to go. Set cursor on line with function/method/struct/etc and run `:GoCmt` to generate a comment.
vim:tw=78:ts=8:noet:ft=help:norl: vim:tw=78:ts=8:noet:ft=help:norl:

View file

@ -25,10 +25,10 @@ end
---@param subcmd string ---@param subcmd string
---@param args string[] ---@param args string[]
---@return string[]|nil ---@return string
function gocmd.run(subcmd, args) function gocmd.run(subcmd, args)
if #args == 0 then if #args == 0 and subcmd ~= "generate" then
error "please provice any arguments" error "please provide any arguments"
end end
if subcmd == "get" then if subcmd == "get" then
@ -39,15 +39,13 @@ function gocmd.run(subcmd, args)
args = if_generate(args) args = if_generate(args)
end end
return r.sync(c.go, { local rs = r.sync { c.go, subcmd, unpack(args) }
args = { subcmd, unpack(args) }, if rs.code ~= 0 then
on_exit = function(data, status) error("go " .. subcmd .. " failed: " .. rs.stderr)
if status ~= 0 then end
error("gocmd failed: " .. data)
end u.notify(c.go .. " " .. subcmd .. " ran successful")
u.notify(c.go .. " " .. subcmd .. " successful runned") return rs.stdout
end,
})
end end
return gocmd return gocmd

View file

@ -1,33 +0,0 @@
local h = vim.health or require "health"
local health = {}
health.start = h.start or h.report_start
health.ok = h.ok or h.report_ok
health.warn = h.warn or h.report_warn
health.error = h.error or h.report_error
health.info = h.info or h.report_info
---@param module string
---@return boolean
function health.is_lualib_found(module)
local is_found, _ = pcall(require, module)
return is_found
end
---@param bin string
---@return boolean
function health.is_binary_found(bin)
if vim.fn.executable(bin) == 1 then
return true
end
return false
end
---@param ft string
---@return boolean
function health.is_treesitter_parser_available(ft)
local ok, parser = pcall(vim.treesitter.get_parser, 0, ft)
return ok and parser ~= nil
end
return health

View file

@ -2,33 +2,40 @@ local c = require "gopher.config"
local log = require "gopher._utils.log" local log = require "gopher._utils.log"
local utils = {} local utils = {}
---@param msg string
---@param lvl number
function utils.deferred_notify(msg, lvl)
vim.defer_fn(function()
vim.notify(msg, lvl, {
title = c.___plugin_name,
})
log.debug(msg)
end, 0)
end
---@param msg string ---@param msg string
---@param lvl? number ---@param lvl? number
function utils.notify(msg, lvl) function utils.notify(msg, lvl)
lvl = lvl or vim.log.levels.INFO lvl = lvl or vim.log.levels.INFO
vim.notify(msg, lvl, { vim.notify(msg, lvl, {
---@diagnostic disable-next-line:undefined-field
title = c.___plugin_name, title = c.___plugin_name,
}) })
log.debug(msg) log.debug(msg)
end end
-- safe require ---@param path string
---@param module string module name ---@return string
function utils.sreq(module) function utils.readfile_joined(path)
local ok, m = pcall(require, module) return table.concat(vim.fn.readfile(path), "\n")
assert(ok, string.format("gopher.nvim dependency error: %s not installed", module)) end
return m
---@param t string[]
---@return string[]
function utils.remove_empty_lines(t)
local res = {}
for _, line in ipairs(t) do
if line ~= "" then
table.insert(res, line)
end
end
return res
end
---@param s string
---@return string
function utils.trimend(s)
local r, _ = string.gsub(s, "%s+$", "")
return r
end end
return utils return utils

View file

@ -18,6 +18,7 @@ local c = require "gopher.config"
local config = { local config = {
-- Name of the plugin. Prepended to log messages -- Name of the plugin. Prepended to log messages
---@diagnostic disable-next-line:undefined-field
name = c.___plugin_name, name = c.___plugin_name,
-- Should print the output to neovim while running -- Should print the output to neovim while running
@ -91,7 +92,7 @@ do
local log_at_level = function(level_config, message_maker, ...) local log_at_level = function(level_config, message_maker, ...)
-- Return early if we're below the current_log_level -- Return early if we're below the current_log_level
-- --
-- the log level source get from config directly because otherwise it doesnt work -- the log level source get from config directly because otherwise it doesn't work
if level_config.level < c.log_level then if level_config.level < c.log_level then
return return
end end

View file

@ -0,0 +1,39 @@
local c = require "gopher.config"
local runner = {}
---@class gopher.RunnerOpts
---@field cwd? string
---@field timeout? number
---@field stdin? boolean|string|string[]
---@field text? boolean
---@param cmd (string|number)[]
---@param on_exit fun(out:vim.SystemCompleted)
---@param opts? gopher.RunnerOpts
---@return vim.SystemObj
function runner.async(cmd, on_exit, opts)
opts = opts or {}
return vim.system(cmd, {
cwd = opts.cwd or nil,
timeout = opts.timeout or c.timeout,
stdin = opts.stdin or nil,
text = opts.text or true,
}, on_exit)
end
---@param cmd (string|number)[]
---@param opts? gopher.RunnerOpts
---@return vim.SystemCompleted
function runner.sync(cmd, opts)
opts = opts or {}
return vim
.system(cmd, {
cwd = opts.cwd or nil,
timeout = opts.timeout or c.timeout,
stdin = opts.stdin or nil,
text = opts.text or true,
})
:wait()
end
return runner

View file

@ -1,33 +0,0 @@
local Job = require "plenary.job"
local runner = {}
---@class gopher.RunnerOpts
---@field args? string[]
---@field cwd? string?
---@field on_exit? fun(data:string, status:number)
---@param cmd string
---@param opts gopher.RunnerOpts
---@return string[]|nil
function runner.sync(cmd, opts)
local output
Job:new({
command = cmd,
args = opts.args,
cwd = opts.cwd,
on_stderr = function(_, data)
vim.print(data)
end,
on_exit = function(data, status)
output = data:result()
vim.schedule(function()
if opts.on_exit then
opts.on_exit(output, status)
end
end)
end,
}):sync(60000 --[[1 min]])
return output
end
return runner

136
lua/gopher/_utils/ts.lua Normal file
View file

@ -0,0 +1,136 @@
local ts = {}
local queries = {
struct = [[
[(type_spec name: (type_identifier) @_name
type: (struct_type))
(var_declaration (var_spec
name: (identifier) @_name @_var
type: (struct_type)))
(short_var_declaration
left: (expression_list (identifier) @_name @_var)
right: (expression_list (composite_literal
type: (struct_type))))]
]],
func = [[
[(function_declaration name: (identifier) @_name)
(method_declaration name: (field_identifier) @_name)]
]],
package = [[
(package_identifier) @_name
]],
interface = [[
(type_spec
name: (type_identifier) @_name
type: (interface_type))
]],
}
---@param parent_type string[]
---@param node TSNode
---@return TSNode?
local function get_parrent_node(parent_type, node)
---@type TSNode?
local current = node
while current do
if vim.tbl_contains(parent_type, current:type()) then
break
end
current = current:parent()
if current == nil then
return nil
end
end
return current
end
---@param query vim.treesitter.Query
---@param node TSNode
---@param bufnr integer
---@return {name:string, is_varstruct:boolean}
local function get_captures(query, node, bufnr)
local res = {}
for id, _node in query:iter_captures(node, bufnr) do
if query.captures[id] == "_name" then
res["name"] = vim.treesitter.get_node_text(_node, bufnr)
end
if query.captures[id] == "_var" then
res["is_varstruct"] = true
end
end
return res
end
---@class gopher.TsResult
---@field name string
---@field start_line integer
---@field end_line integer
---@field is_varstruct boolean
---@param bufnr integer
---@param parent_type string[]
---@param query string
---@return gopher.TsResult
local function do_stuff(bufnr, parent_type, query)
if not vim.treesitter.get_parser(bufnr, "go") then
error "No treesitter parser found for go"
end
local node = vim.treesitter.get_node {
bufnr = bufnr,
}
if not node then
error "No nodes found under cursor"
end
local parent_node = get_parrent_node(parent_type, node)
if not parent_node then
error "No parent node found under cursor"
end
local q = vim.treesitter.query.parse("go", query)
local res = get_captures(q, parent_node, bufnr)
assert(res.name ~= nil, "No capture name found")
local start_row, _, end_row, _ = parent_node:range()
res["start_line"] = start_row + 1
res["end_line"] = end_row + 1
return res
end
---@param bufnr integer
function ts.get_struct_under_cursor(bufnr)
--- should be both type_spec and type_declaration
--- because in cases like `type ( T struct{}, U strict{} )`
--- i will be choosing always last struct in the list
---
--- var_declaration is for cases like `var x struct{}`
--- short_var_declaration is for cases like `x := struct{}{}`
return do_stuff(bufnr, {
"type_spec",
"type_declaration",
"var_declaration",
"short_var_declaration",
}, queries.struct)
end
---@param bufnr integer
function ts.get_func_under_cursor(bufnr)
--- since this handles both and funcs and methods we should check for both parent nodes
return do_stuff(bufnr, { "function_declaration", "method_declaration" }, queries.func)
end
---@param bufnr integer
function ts.get_package_under_cursor(bufnr)
return do_stuff(bufnr, { "package_clause" }, queries.package)
end
---@param bufnr integer
function ts.get_interface_under_cursor(bufnr)
return do_stuff(bufnr, { "type_declaration" }, queries.interface)
end
return ts

View file

@ -1,104 +0,0 @@
---@diagnostic disable: param-type-mismatch
local nodes = require "gopher._utils.ts.nodes"
local u = require "gopher._utils"
local ts = {
querys = {
struct_block = [[((type_declaration (type_spec name:(type_identifier) @struct.name type: (struct_type)))@struct.declaration)]],
em_struct_block = [[(field_declaration name:(field_identifier)@struct.name type: (struct_type)) @struct.declaration]],
package = [[(package_clause (package_identifier)@package.name)@package.clause]],
interface = [[((type_declaration (type_spec name:(type_identifier) @interface.name type:(interface_type)))@interface.declaration)]],
method_name = [[((method_declaration receiver: (parameter_list)@method.receiver name: (field_identifier)@method.name body:(block))@method.declaration)]],
func = [[((function_declaration name: (identifier)@function.name) @function.declaration)]],
},
}
---@return table
local function get_name_defaults()
return {
["func"] = "function",
["if"] = "if",
["else"] = "else",
["for"] = "for",
}
end
---@param row string
---@param col string
---@param bufnr string|nil
---@param do_notify boolean|nil
---@return table|nil
function ts.get_struct_node_at_pos(row, col, bufnr, do_notify)
local notify = do_notify or true
local query = ts.querys.struct_block .. " " .. ts.querys.em_struct_block
local bufn = bufnr or vim.api.nvim_get_current_buf()
local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col)
if ns == nil then
if notify then
u.deferred_notify("struct not found", vim.log.levels.WARN)
end
else
return ns[#ns]
end
end
---@param row string
---@param col string
---@param bufnr string|nil
---@param do_notify boolean|nil
---@return table|nil
function ts.get_func_method_node_at_pos(row, col, bufnr, do_notify)
local notify = do_notify or true
local query = ts.querys.func .. " " .. ts.querys.method_name
local bufn = bufnr or vim.api.nvim_get_current_buf()
local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col)
if ns == nil then
if notify then
u.deferred_notify("function not found", vim.log.levels.WARN)
end
else
return ns[#ns]
end
end
---@param row string
---@param col string
---@param bufnr string|nil
---@param do_notify boolean|nil
---@return table|nil
function ts.get_package_node_at_pos(row, col, bufnr, do_notify)
local notify = do_notify or true
-- stylua: ignore
if row > 10 then return end
local query = ts.querys.package
local bufn = bufnr or vim.api.nvim_get_current_buf()
local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col)
if ns == nil then
if notify then
u.deferred_notify("package not found", vim.log.levels.WARN)
return nil
end
else
return ns[#ns]
end
end
---@param row string
---@param col string
---@param bufnr string|nil
---@param do_notify boolean|nil
---@return table|nil
function ts.get_interface_node_at_pos(row, col, bufnr, do_notify)
local notify = do_notify or true
local query = ts.querys.interface
local bufn = bufnr or vim.api.nvim_get_current_buf()
local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col)
if ns == nil then
if notify then
u.deferred_notify("interface not found", vim.log.levels.WARN)
end
else
return ns[#ns]
end
end
return ts

View file

@ -1,143 +0,0 @@
local ts_query = require "nvim-treesitter.query"
local parsers = require "nvim-treesitter.parsers"
local locals = require "nvim-treesitter.locals"
local u = require "gopher._utils"
local M = {}
local function intersects(row, col, sRow, sCol, eRow, eCol)
if sRow > row or eRow < row then
return false
end
if sRow == row and sCol > col then
return false
end
if eRow == row and eCol < col then
return false
end
return true
end
---@param nodes table
---@param row string
---@param col string
---@return table
function M.intersect_nodes(nodes, row, col)
local found = {}
for idx = 1, #nodes do
local node = nodes[idx]
local sRow = node.dim.s.r
local sCol = node.dim.s.c
local eRow = node.dim.e.r
local eCol = node.dim.e.c
if intersects(row, col, sRow, sCol, eRow, eCol) then
table.insert(found, node)
end
end
return found
end
---@param nodes table
---@return table
function M.sort_nodes(nodes)
table.sort(nodes, function(a, b)
return M.count_parents(a) < M.count_parents(b)
end)
return nodes
end
---@param query string
---@param lang string
---@param bufnr integer
---@param pos_row string
---@return string
function M.get_all_nodes(query, lang, _, bufnr, pos_row, _)
bufnr = bufnr or 0
pos_row = pos_row or 30000
local ok, parsed_query = pcall(function()
return vim.treesitter.query.parse(lang, query)
end)
if not ok then
return nil
end
local parser = parsers.get_parser(bufnr, lang)
local root = parser:parse()[1]:root()
local start_row, _, end_row, _ = root:range()
local results = {}
for match in ts_query.iter_prepared_matches(parsed_query, root, bufnr, start_row, end_row) do
local sRow, sCol, eRow, eCol, declaration_node
local type, name, op = "", "", ""
locals.recurse_local_nodes(match, function(_, node, path)
local idx = string.find(path, ".[^.]*$")
op = string.sub(path, idx + 1, #path)
type = string.sub(path, 1, idx - 1)
if op == "name" then
name = vim.treesitter.get_node_text(node, bufnr)
elseif op == "declaration" or op == "clause" then
declaration_node = node
sRow, sCol, eRow, eCol = node:range()
sRow = sRow + 1
eRow = eRow + 1
sCol = sCol + 1
eCol = eCol + 1
end
end)
if declaration_node ~= nil then
table.insert(results, {
declaring_node = declaration_node,
dim = { s = { r = sRow, c = sCol }, e = { r = eRow, c = eCol } },
name = name,
operator = op,
type = type,
})
end
end
return results
end
---@param query string
---@param default string
---@param bufnr string
---@param row string
---@param col string
---@return table
function M.nodes_at_cursor(query, default, bufnr, row, col)
bufnr = bufnr or vim.api.nvim_get_current_buf()
local ft = vim.api.nvim_buf_get_option(bufnr, "ft")
if row == nil or col == nil then
row, col = unpack(vim.api.nvim_win_get_cursor(0))
end
local nodes = M.get_all_nodes(query, ft, default, bufnr, row, col)
if nodes == nil then
u.deferred_notify(
"Unable to find any nodes. Place your cursor on a go symbol and try again",
vim.log.levels.DEBUG
)
return nil
end
nodes = M.sort_nodes(M.intersect_nodes(nodes, row, col))
if nodes == nil or #nodes == 0 then
u.deferred_notify(
"Unable to find any nodes at pos. " .. tostring(row) .. ":" .. tostring(col),
vim.log.levels.DEBUG
)
return nil
end
return nodes
end
return M

View file

@ -1,59 +1,57 @@
---@toc_entry Generate comments ---@toc_entry Generate comments
---@tag gopher.nvim-comments ---@tag gopher.nvim-comments
---@usage Execute `:GoCmt` to generate a comment for the current function/method/struct/etc on this line. ---@text
---@text This module provides a way to generate comments for Go code. --- This module provides a way to generate comments for Go code.
---
---@usage Set cursor on line with function/method/struct/etc and run `:GoCmt` to generate a comment.
local ts = require "gopher._utils.ts"
local log = require "gopher._utils.log" local log = require "gopher._utils.log"
local comment = {}
local function generate(row, col) ---@param name string
local ts_utils = require "gopher._utils.ts" ---@return string
local comment, ns = nil, nil ---@dochide
local function template(name)
ns = ts_utils.get_package_node_at_pos(row, col, nil, false) return "// " .. name .. " "
if ns ~= nil then
comment = "// Package " .. ns.name .. " provides " .. ns.name
return comment, ns
end
ns = ts_utils.get_struct_node_at_pos(row, col, nil, false)
if ns ~= nil then
comment = "// " .. ns.name .. " " .. ns.type .. " "
return comment, ns
end
ns = ts_utils.get_func_method_node_at_pos(row, col, nil, false)
if ns ~= nil then
comment = "// " .. ns.name .. " " .. ns.type .. " "
return comment, ns
end
ns = ts_utils.get_interface_node_at_pos(row, col, nil, false)
if ns ~= nil then
comment = "// " .. ns.name .. " " .. ns.type .. " "
return comment, ns
end
return "// ", {}
end end
return function() ---@param bufnr integer
local row, col = unpack(vim.api.nvim_win_get_cursor(0)) ---@return string
local comment, ns = generate(row + 1, col + 1) ---@dochide
local function generate(bufnr)
local s_ok, s_res = pcall(ts.get_struct_under_cursor, bufnr)
if s_ok then
return template(s_res.name)
end
log.debug("generated comment: " .. comment) local f_ok, f_res = pcall(ts.get_func_under_cursor, bufnr)
if f_ok then
return template(f_res.name)
end
vim.api.nvim_win_set_cursor(0, { local i_ok, i_res = pcall(ts.get_interface_under_cursor, bufnr)
ns.dim.s.r, if i_ok then
ns.dim.s.c, return template(i_res.name)
}) end
---@diagnostic disable-next-line: param-type-mismatch local p_ok, p_res = pcall(ts.get_package_under_cursor, bufnr)
vim.fn.append(row - 1, comment) if p_ok then
return "// Package " .. p_res.name .. " provides "
end
vim.api.nvim_win_set_cursor(0, { return "// "
ns.dim.s.r,
#comment + 1,
})
vim.cmd [[startinsert!]]
end end
function comment.comment()
local bufnr = vim.api.nvim_get_current_buf()
local cmt = generate(bufnr)
log.debug("generated comment: " .. cmt)
local pos = vim.fn.getcurpos()[2]
vim.fn.append(pos - 1, cmt)
vim.fn.setpos(".", { 0, pos, #cmt })
vim.cmd "startinsert!"
end
return comment

View file

@ -1,17 +1,9 @@
---@toc_entry Configuration
---@tag gopher.nvim-config
---@text config it is the place where you can configure the plugin.
--- also this is optional is you're ok with default settings.
--- You can look at default options |gopher.nvim-config-defaults|
---@type gopher.Config
---@private
local config = {} local config = {}
---@tag gopher.nvim-config.ConfigGoTagTransform ---@tag gopher.nvim-config.ConfigGoTagTransform
---@text Possible values for |gopher.Config|.gotag.transform: ---@text Possible values for |gopher.Config|.gotag.transform:
--- ---
---@private ---@dochide
---@alias gopher.ConfigGoTagTransform ---@alias gopher.ConfigGoTagTransform
---| "snakecase" "GopherUser" -> "gopher_user" ---| "snakecase" "GopherUser" -> "gopher_user"
---| "camelcase" "GopherUser" -> "gopherUser" ---| "camelcase" "GopherUser" -> "gopherUser"
@ -20,19 +12,19 @@ local config = {}
---| "titlecase" "GopherUser" -> "Gopher User" ---| "titlecase" "GopherUser" -> "Gopher User"
---| "keep" keeps the original field name ---| "keep" keeps the original field name
--minidoc_replace_start { ---@toc_entry Config
---@tag gopher.nvim-config
---@tag gopher.nvim-config-defaults ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section):gsub(">", ">lua")
---
---@class gopher.Config ---@class gopher.Config
local default_config = { local default_config = {
--minidoc_replace_end -- log level, you might consider using DEBUG or TRACE for debugging the plugin
-- log level, you might consider using DEBUG or TRACE for degugging the plugin
---@type number ---@type number
log_level = vim.log.levels.INFO, log_level = vim.log.levels.INFO,
-- timeout for running internal commands
---@type number
timeout = 2000,
-- user specified paths to binaries -- user specified paths to binaries
---@class gopher.ConfigCommand ---@class gopher.ConfigCommand
commands = { commands = {
@ -41,7 +33,6 @@ local default_config = {
gotests = "gotests", gotests = "gotests",
impl = "impl", impl = "impl",
iferr = "iferr", iferr = "iferr",
dlv = "dlv",
}, },
---@class gopher.ConfigGotests ---@class gopher.ConfigGotests
gotests = { gotests = {
@ -51,33 +42,61 @@ local default_config = {
---@type string|nil ---@type string|nil
template_dir = nil, template_dir = nil,
-- switch table tests from using slice to map (with test name for the key) -- switch table tests from using slice to map (with test name for the key)
-- works only with gotests installed from develop branch
named = false, named = false,
}, },
---@class gopher.ConfigGoTag ---@class gopher.ConfigGoTag
gotag = { gotag = {
---@type gopher.ConfigGoTagTransform ---@type gopher.ConfigGoTagTransform
transform = "snakecase", transform = "snakecase",
-- default tags to add to struct fields
default_tag = "json",
},
iferr = {
-- choose a custom error message
---@type string|nil
message = nil,
}, },
} }
--minidoc_afterlines_end --minidoc_afterlines_end
---@type gopher.Config ---@type gopher.Config
---@private ---@dochide
local _config = default_config local _config = default_config
-- I am kinda secret so don't tell anyone about me -- I am kinda secret so don't tell anyone about me even dont use me
-- even dont use me
-- --
-- if you don't belive me that i am secret see -- if you don't believe me that i am secret see
-- the line below it says @private -- the line below it says @private
---@private ---@private
_config.___plugin_name = "gopher.nvim" ---@diagnostic disable-line: inject-field _config.___plugin_name = "gopher.nvim" ---@diagnostic disable-line: inject-field
---@param user_config? gopher.Config ---@param user_config? gopher.Config
---@private ---@dochide
function config.setup(user_config) function config.setup(user_config)
_config = vim.tbl_deep_extend("force", default_config, user_config or {}) vim.validate { user_config = { user_config, "table", true } }
_config = vim.tbl_deep_extend("force", vim.deepcopy(default_config), user_config or {})
vim.validate {
log_level = { _config.log_level, "number" },
timeout = { _config.timeout, "number" },
["commands"] = { _config.commands, "table" },
["commands.go"] = { _config.commands.go, "string" },
["commands.gomodifytags"] = { _config.commands.gomodifytags, "string" },
["commands.gotests"] = { _config.commands.gotests, "string" },
["commands.impl"] = { _config.commands.impl, "string" },
["commands.iferr"] = { _config.commands.iferr, "string" },
["gotests"] = { _config.gotests, "table" },
["gotests.template"] = { _config.gotests.template, "string" },
["gotests.template_dir"] = { _config.gotests.template, "string", true },
["gotests.named"] = { _config.gotests.named, "boolean" },
["gotag"] = { _config.gotag, "table" },
["gotag.transform"] = { _config.gotag.transform, "string" },
["gotag.default_tag"] = { _config.gotag.default_tag, "string" },
["iferr"] = { _config.iferr, "table" },
["iferr.message"] = { _config.iferr.message, "string", true },
}
end end
setmetatable(config, { setmetatable(config, {
@ -86,4 +105,6 @@ setmetatable(config, {
end, end,
}) })
---@dochide
---@return gopher.Config
return config return config

View file

@ -1,129 +0,0 @@
---@toc_entry Setup `nvim-dap` for Go
---@tag gopher.nvim-dap
---@text This module sets up `nvim-dap` for Go.
---@usage just call `require("gopher.dap").setup()`, and you're good to go.
local c = require "gopher.config"
local dap = {}
dap.adapter = function(callback, config)
local host = config.host or "127.0.0.1"
local port = config.port or "38697"
local addr = string.format("%s:%s", host, port)
local handle, pid_or_err
local stdout = assert(vim.loop.new_pipe(false))
local opts = {
stdio = { nil, stdout },
args = { "dap", "-l", addr },
detached = true,
}
handle, pid_or_err = vim.loop.spawn(c.commands.dlv, opts, function(status)
if not stdout or not handle then
return
end
stdout:close()
handle:close()
if status ~= 0 then
print("dlv exited with code", status)
end
end)
assert(handle, "Error running dlv: " .. tostring(pid_or_err))
if stdout then
stdout:read_start(function(err, chunk)
assert(not err, err)
if chunk then
vim.schedule(function()
require("dap.repl").append(chunk)
end)
end
end)
end
-- wait for delve to start
vim.defer_fn(function()
callback { type = "server", host = "127.0.0.1", port = port }
end, 100)
end
local function args_input()
vim.ui.input({ prompt = "Args: " }, function(input)
return vim.split(input or "", " ")
end)
end
local function get_arguments()
local co = coroutine.running()
if co then
return coroutine.create(function()
local args = args_input()
coroutine.resume(co, args)
end)
else
return args_input()
end
end
dap.configuration = {
{
type = "go",
name = "Debug",
request = "launch",
program = "${file}",
},
{
type = "go",
name = "Debug (Arguments)",
request = "launch",
program = "${file}",
args = get_arguments,
},
{
type = "go",
name = "Debug Package",
request = "launch",
program = "${fileDirname}",
},
{
type = "go",
name = "Attach",
mode = "local",
request = "attach",
processId = require("dap.utils").pick_process,
},
{
type = "go",
name = "Debug test",
request = "launch",
mode = "test",
program = "${file}",
},
{
type = "go",
name = "Debug test (go.mod)",
request = "launch",
mode = "test",
program = "./${relativeFileDirname}",
},
}
-- sets ups nvim-dap for Go in one function call.
function dap.setup()
vim.deprecate(
"gopher.dap",
"you might consider setting up `nvim-dap` manually, or using another plugin(https://github.com/leoluz/nvim-dap-go)",
"v0.1.6",
"gopher"
)
local ok, d = pcall(require, "dap")
assert(ok, "gopher.nvim dependency error: dap not installed")
d.adapters.go = dap.adapter
d.configurations.go = dap.configuration
end
return dap

View file

@ -2,42 +2,20 @@
---@tag gopher.nvim-gotests ---@tag gopher.nvim-gotests
---@text gotests is utilizing the `gotests` tool to generate unit tests boilerplate. ---@text gotests is utilizing the `gotests` tool to generate unit tests boilerplate.
---@usage ---@usage
--- - generate unit test for spesisfic function/method --- - Generate unit test for specific function/method:
--- - to specift the function/method put your cursor on it --- 1. Place your cursor on the desired function/method.
--- - run `:GoTestAdd` --- 2. Run `:GoTestAdd`
--- ---
--- - generate unit tests for all functions/methods in current file --- - Generate unit tests for *all* functions/methods in current file:
--- - run `:GoTestsAll` --- - run `:GoTestsAll`
--- ---
--- - generate unit tests only for exported(public) functions/methods --- - Generate unit tests *only* for *exported(public)* functions/methods:
--- - run `:GoTestsExp` --- - run `:GoTestsExp`
--- ---
--- you can also specify the template to use for generating the tests. see |gopher.nvim-config| --- You can also specify the template to use for generating the tests. See |gopher.nvim-config|
--- more details about templates can be found at: https://github.com/cweill/gotests --- More details about templates can be found at: https://github.com/cweill/gotests
--- ---
--- If you prefer named tests, you can enable them in |gopher.nvim-config|.
---@tag gopher.nvim-gotests-named
---@text
--- if you prefare using named tests, you can enable it in the config.
--- but you would need to install `gotests@develop` because stable version doesn't support this feature.
--- you can do it with:
--- >lua
--- -- simply run go get in your shell:
--- go install github.com/cweill/gotests/...@develop
---
--- -- if you want to install it within neovim, you can use one of this:
---
--- vim.fn.jobstart("go install github.com/cweill/gotests/...@develop")
---
--- -- or if you want to use mason:
--- require("mason-tool-installer").setup {
--- ensure_installed = {
--- { "gotests", version = "develop" },
--- }
--- }
--- <
---
--- if you choose to install `gotests` within neovim, i recommend adding it to your `build` section in your |lazy.nvim|
local c = require "gopher.config" local c = require "gopher.config"
local ts_utils = require "gopher._utils.ts" local ts_utils = require "gopher._utils.ts"
@ -47,7 +25,7 @@ local log = require "gopher._utils.log"
local gotests = {} local gotests = {}
---@param args table ---@param args table
---@private ---@dochide
local function add_test(args) local function add_test(args)
if c.gotests.named then if c.gotests.named then
table.insert(args, "-named") table.insert(args, "-named")
@ -68,27 +46,20 @@ local function add_test(args)
log.debug("generating tests with args: ", args) log.debug("generating tests with args: ", args)
return r.sync(c.commands.gotests, { local rs = r.sync { c.commands.gotests, unpack(args) }
args = args, if rs.code ~= 0 then
on_exit = function(data, status) error("gotests failed: " .. rs.stderr)
if not status == 0 then end
error("gotests failed: " .. data)
end
u.notify "unit test(s) generated" u.notify "unit test(s) generated"
end,
})
end end
-- generate unit test for one function -- generate unit test for one function
function gotests.func_test() function gotests.func_test()
local ns = ts_utils.get_func_method_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0))) local bufnr = vim.api.nvim_get_current_buf()
if ns == nil or ns.name == nil then local func = ts_utils.get_func_under_cursor(bufnr)
u.notify("cursor on func/method and execute the command again", vim.log.levels.WARN)
return
end
add_test { "-only", ns.name } add_test { "-only", func.name }
end end
-- generate unit tests for all functions in current file -- generate unit tests for all functions in current file

View file

@ -1,12 +1,9 @@
local health = {} local health = {}
local cmd = require("gopher.config").commands local cmd = require("gopher.config").commands
local u = require "gopher._utils.health_util"
local deps = { local deps = {
plugin = { plugin = {
{ lib = "dap", msg = "required for `gopher.dap`", optional = true }, { lib = "nvim-treesitter", msg = "required for everything in gopher.nvim" },
{ lib = "plenary", msg = "required for everyting in gopher.nvim", optional = false },
{ lib = "nvim-treesitter", msg = "required for everyting in gopher.nvim", optional = false },
}, },
bin = { bin = {
{ {
@ -14,55 +11,70 @@ local deps = {
msg = "required for `:GoGet`, `:GoMod`, `:GoGenerate`, `:GoWork`, `:GoInstallDeps`", msg = "required for `:GoGet`, `:GoMod`, `:GoGenerate`, `:GoWork`, `:GoInstallDeps`",
optional = false, optional = false,
}, },
{ bin = cmd.gomodifytags, msg = "required for `:GoTagAdd`, `:GoTagRm`", optional = false }, { bin = cmd.gomodifytags, msg = "required for `:GoTagAdd`, `:GoTagRm`", optional = true },
{ bin = cmd.impl, msg = "required for `:GoImpl`", optional = false }, { bin = cmd.impl, msg = "required for `:GoImpl`", optional = true },
{ bin = cmd.iferr, msg = "required for `:GoIfErr`", optional = false }, { bin = cmd.iferr, msg = "required for `:GoIfErr`", optional = true },
{ {
bin = cmd.gotests, bin = cmd.gotests,
msg = "required for `:GoTestAdd`, `:GoTestsAll`, `:GoTestsExp`", msg = "required for `:GoTestAdd`, `:GoTestsAll`, `:GoTestsExp`",
optional = false, optional = true,
}, },
{ bin = cmd.dlv, msg = "required for debugging, (`nvim-dap`, `gopher.dap`)", optional = true },
}, },
treesitter = { treesitter = {
{ parser = "go", msg = "required for `gopher.nvim`", optional = false }, { parser = "go", msg = "required for `gopher.nvim`" },
}, },
} }
---@param module string
---@return boolean
local function is_lualib_found(module)
local is_found, _ = pcall(require, module)
return is_found
end
---@param bin string
---@return boolean
local function is_binary_found(bin)
return vim.fn.executable(bin) == 1
end
---@param ft string
---@return boolean
local function is_treesitter_parser_available(ft)
local ok, parser = pcall(vim.treesitter.get_parser, 0, ft)
return ok and parser ~= nil
end
function health.check() function health.check()
u.start "required plugins" vim.health.start "required plugins"
for _, plugin in ipairs(deps.plugin) do for _, plugin in ipairs(deps.plugin) do
if u.is_lualib_found(plugin.lib) then if is_lualib_found(plugin.lib) then
u.ok(plugin.lib .. " installed") vim.health.ok(plugin.lib .. " installed")
else else
if plugin.optional then vim.health.error(plugin.lib .. " not found, " .. plugin.msg)
u.warn(plugin.lib .. " not found, " .. plugin.msg)
else
u.error(plugin.lib .. " not found, " .. plugin.msg)
end
end end
end end
u.start "required binaries" vim.health.start "required binaries"
u.info "all those binaries can be installed by `:GoInstallDeps`" vim.health.info "all those binaries can be installed by `:GoInstallDeps`"
for _, bin in ipairs(deps.bin) do for _, bin in ipairs(deps.bin) do
if u.is_binary_found(bin.bin) then if is_binary_found(bin.bin) then
u.ok(bin.bin .. " installed") vim.health.ok(bin.bin .. " installed")
else else
if bin.optional then if bin.optional then
u.warn(bin.bin .. " not found, " .. bin.msg) vim.health.warn(bin.bin .. " not found, " .. bin.msg)
else else
u.error(bin.bin .. " not found, " .. bin.msg) vim.health.error(bin.bin .. " not found, " .. bin.msg)
end end
end end
end end
u.start "required treesitter parsers" vim.health.start "required treesitter parsers"
for _, parser in ipairs(deps.treesitter) do for _, parser in ipairs(deps.treesitter) do
if u.is_treesitter_parser_available(parser.parser) then if is_treesitter_parser_available(parser.parser) then
u.ok(parser.parser .. " parser installed") vim.health.ok(parser.parser .. " parser installed")
else else
u.error(parser.parser .. " parser not found, " .. parser.msg) vim.health.error(parser.parser .. " parser not found, " .. parser.msg)
end end
end end
end end

View file

@ -1,30 +1,46 @@
-- Thanks https://github.com/koron/iferr for vim implementation
---@toc_entry Iferr ---@toc_entry Iferr
---@tag gopher.nvim-iferr ---@tag gopher.nvim-iferr
---@text if you're using `iferr` tool, this module provides a way to automatically insert `if err != nil` check. ---@text
---@usage execute `:GoIfErr` near any err variable to insert the check --- `iferr` provides a way to way to automatically insert `if err != nil` check.
--- If you want to change `-message` option of `iferr` tool, see |gopher.nvim-config|
---
---@usage Execute `:GoIfErr` near any `err` variable to insert the check
local c = require "gopher.config" local c = require "gopher.config"
local u = require "gopher._utils"
local r = require "gopher._utils.runner"
local log = require "gopher._utils.log" local log = require "gopher._utils.log"
local iferr = {} local iferr = {}
-- That's Lua implementation: github.com/koron/iferr
function iferr.iferr() function iferr.iferr()
local boff = vim.fn.wordcount().cursor_bytes local curb = vim.fn.wordcount().cursor_bytes
local pos = vim.fn.getcurpos()[2] local pos = vim.fn.getcurpos()[2]
local fpath = vim.fn.expand "%"
local data = vim.fn.systemlist((c.commands.iferr .. " -pos " .. boff), vim.fn.bufnr "%") local cmd = { c.commands.iferr, "-pos", curb }
if vim.v.shell_error ~= 0 then if c.iferr.message ~= nil and type(c.iferr.message) == "string" then
if string.find(data[1], "no functions at") then table.insert(cmd, "-message")
vim.print "no function found" table.insert(cmd, c.iferr.message)
log.warn("iferr: no function at " .. boff) end
local rs = r.sync(cmd, {
stdin = u.readfile_joined(fpath),
})
if rs.code ~= 0 then
if string.find(rs.stderr, "no functions at") then
u.notify("iferr: no function at " .. curb, vim.log.levels.ERROR)
log.warn("iferr: no function at " .. curb)
return return
end end
log.error("failed. output: " .. vim.inspect(data)) log.error("ferr: failed. output: " .. rs.stderr)
error("iferr failed: " .. vim.inspect(data)) error("iferr failed: " .. rs.stderr)
end end
vim.fn.append(pos, data) vim.fn.append(pos, u.remove_empty_lines(vim.split(rs.stdout, "\n")))
vim.cmd [[silent normal! j=2j]] vim.cmd [[silent normal! j=2j]]
vim.fn.setpos(".", pos) vim.fn.setpos(".", pos)
end end

View file

@ -1,20 +1,27 @@
---@toc_entry Auto implementation of interface methods ---@toc_entry Auto implementation of interface methods
---@tag gopher.nvim-impl ---@tag gopher.nvim-impl
---@text impl is utilizing the `impl` tool to generate method stubs for interfaces. ---@text
---@usage --- Integration of `impl` tool to generate method stubs for interfaces.
--- 1. put your coursor on the struct on which you want implement the interface
--- and run `:GoImpl io.Reader`
--- which will automatically choose the reciver for the methods and
--- implement the `io.Reader` interface
--- 2. same as previous but with custom receiver, so put your coursor on the struct
--- run `:GoImpl w io.Writer`
--- where `w` is the receiver and `io.Writer` is the interface
--- 3. specift receiver, struct, and interface
--- there's no need to put your coursor on the struct if you specify all arguments
--- `:GoImpl r RequestReader io.Reader`
--- where `r` is the receiver, `RequestReader` is the struct and `io.Reader` is the interface
--- ---
--- simple example: ---@usage 1. Automatically implement an interface for a struct:
--- - Place your cursor on the struct where you want to implement the interface.
--- - Run `:GoImpl io.Reader`
--- - This will automatically determine the receiver and implement the `io.Reader` interface.
---
--- 2. Specify a custom receiver:
--- - Place your cursor on the struct
--- - Run `:GoImpl w io.Writer`, where:
--- - `w` is the receiver.
--- - `io.Writer` is the interface to implement.
---
--- 3. Explicitly specify the receiver, struct, and interface:
--- - No need to place the cursor on the struct if all arguments are provided.
--- - Run `:GoImpl r RequestReader io.Reader`, where:
--- - `r` is the receiver.
--- - `RequestReader` is the struct.
--- - `io.Reader` is the interface to implement.
---
--- Example:
--- >go --- >go
--- type BytesReader struct{} --- type BytesReader struct{}
--- // ^ put your cursor here --- // ^ put your cursor here
@ -22,7 +29,7 @@
--- ---
--- // this is what you will get --- // this is what you will get
--- func (b *BytesReader) Read(p []byte) (n int, err error) { --- func (b *BytesReader) Read(p []byte) (n int, err error) {
--- panic("not implemented") // TODO: Implement --- panic("not implemented") // TODO: Implement
--- } --- }
--- < --- <
@ -32,65 +39,32 @@ local ts_utils = require "gopher._utils.ts"
local u = require "gopher._utils" local u = require "gopher._utils"
local impl = {} local impl = {}
---@return string
---@private
local function get_struct()
local ns = ts_utils.get_struct_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0)))
if ns == nil then
u.deferred_notify("put cursor on a struct or specify a receiver", vim.log.levels.INFO)
return ""
end
vim.api.nvim_win_set_cursor(0, {
ns.dim.e.r,
ns.dim.e.c,
})
return ns.name
end
function impl.impl(...) function impl.impl(...)
local args = { ... } local args = { ... }
local iface, recv_name = "", "" local iface, recv = "", ""
local recv = get_struct() local bufnr = vim.api.nvim_get_current_buf()
if #args == 0 then if #args == 1 then -- :GoImpl io.Reader
iface = vim.fn.input "impl: generating method stubs for interface: " local st = ts_utils.get_struct_under_cursor(bufnr)
vim.cmd "redraw!" iface = args[1]
if iface == "" then recv = string.lower(st.name) .. " *" .. st.name
u.deferred_notify("usage: GoImpl f *File io.Reader", vim.log.levels.INFO)
return
end
elseif #args == 1 then -- :GoImpl io.Reader
recv = string.lower(recv) .. " *" .. recv
vim.cmd "redraw!"
iface = select(1, ...)
elseif #args == 2 then -- :GoImpl w io.Writer elseif #args == 2 then -- :GoImpl w io.Writer
recv_name = select(1, ...) local st = ts_utils.get_struct_under_cursor(bufnr)
recv = string.format("%s *%s", recv_name, recv) iface = args[2]
iface = select(#args, ...) recv = args[1] .. " *" .. st.name
elseif #args > 2 then elseif #args == 3 then -- :GoImpl r Struct io.Reader
iface = select(#args, ...) recv = args[1] .. " *" .. args[2]
recv = select(#args - 1, ...) iface = args[3]
recv_name = select(#args - 2, ...)
recv = string.format("%s %s", recv_name, recv)
end end
local output = r.sync(c.impl, { local rs = r.sync { c.impl, "-dir", vim.fn.fnameescape(vim.fn.expand "%:p:h"), recv, iface }
args = { if rs.code ~= 0 then
"-dir", error("failed to implement interface: " .. rs.stderr)
vim.fn.fnameescape(vim.fn.expand "%:p:h" --[[@as string]]), end
recv,
iface,
},
on_exit = function(data, status)
if not status == 0 then
error("impl failed: " .. data)
end
end,
})
local pos = vim.fn.getcurpos()[2] local pos = vim.fn.getcurpos()[2]
local output = u.remove_empty_lines(vim.split(rs.stdout, "\n"))
table.insert(output, 1, "") table.insert(output, 1, "")
vim.fn.append(pos, output) vim.fn.append(pos, output)
end end

View file

@ -1,28 +1,32 @@
--- *gopher.nvim* --- *gopher.nvim* Enhance your golang experience
---
--- MIT License Copyright (c) 2025 Oleksandr Smirnov
--- ---
--- ============================================================================== --- ==============================================================================
--- ---
--- gopher.nvim is a minimalistic plugin for Go development in Neovim written in Lua. --- gopher.nvim is a minimalistic plugin for Go development in Neovim written in Lua.
--- It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim. --- It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim.
---
--- Table of Contents --- Table of Contents
---@tag gopher.nvim-table-of-contents
---@toc ---@toc
local log = require "gopher._utils.log" local log = require "gopher._utils.log"
local tags = require "gopher.struct_tags" local tags = require "gopher.struct_tags"
local tests = require "gopher.gotests" local tests = require "gopher.gotests"
local gocmd = require("gopher._utils.runner.gocmd").run local gocmd = require("gopher._utils.gocmd").run
local gopher = {} local gopher = {}
---@toc_entry Setup ---@toc_entry Setup
---@tag gopher.nvim-setup ---@tag gopher.nvim-setup()
---@text Setup function. This method simply merges default configs with opts table. ---@text Setup function. This method simply merges default config with opts table.
--- You can read more about configuration at |gopher.nvim-config| --- You can read more about configuration at |gopher.nvim-config|
--- Calling this function is optional, if you ok with default settings. Look |gopher.nvim.config-defaults| --- Calling this function is optional, if you ok with default settings.
--- See |gopher.nvim.config|
--- ---
---@usage `require("gopher").setup {}` (replace `{}` with your `config` table) ---@usage >lua
---@param user_config gopher.Config --- require("gopher").setup {} -- use default config or replace {} with your own
--- <
---@param user_config gopher.Config See |gopher.nvim-config|
gopher.setup = function(user_config) gopher.setup = function(user_config)
log.debug "setting up config" log.debug "setting up config"
require("gopher.config").setup(user_config) require("gopher.config").setup(user_config)
@ -30,19 +34,22 @@ gopher.setup = function(user_config)
end end
---@toc_entry Install dependencies ---@toc_entry Install dependencies
---@tag gopher.nvim-install-deps ---@tag gopher.nvim-dependencies
---@text Gopher.nvim implements most of its features using third-party tools. ---@text Gopher.nvim implements most of its features using third-party tools.
--- To install these tools, you can run `:GoInstallDeps` command --- To install these tools, you can run `:GoInstallDeps` command
--- or call `require("gopher").install_deps()` if you want ues lua api. --- or call `require("gopher").install_deps()` if you want to use lua api.
--- By default dependencies will be installed asynchronously,
--- to install them synchronously pass `{sync = true}` as an argument.
gopher.install_deps = require("gopher.installer").install_deps gopher.install_deps = require("gopher.installer").install_deps
gopher.impl = require("gopher.impl").impl gopher.impl = require("gopher.impl").impl
gopher.iferr = require("gopher.iferr").iferr gopher.iferr = require("gopher.iferr").iferr
gopher.comment = require "gopher.comment" gopher.comment = require("gopher.comment").comment
gopher.tags = { gopher.tags = {
add = tags.add, add = tags.add,
rm = tags.remove, rm = tags.remove,
clear = tags.clear,
} }
gopher.test = { gopher.test = {
@ -52,19 +59,19 @@ gopher.test = {
} }
gopher.get = function(...) gopher.get = function(...)
gocmd("get", { ... }) gocmd("get", ...)
end end
gopher.mod = function(...) gopher.mod = function(...)
gocmd("mod", { ... }) gocmd("mod", ...)
end end
gopher.generate = function(...) gopher.generate = function(...)
gocmd("generate", { ... }) gocmd("generate", ...)
end end
gopher.work = function(...) gopher.work = function(...)
gocmd("work", { ... }) gocmd("work", ...)
end end
return gopher return gopher

View file

@ -1,35 +1,56 @@
local c = require("gopher.config").commands local c = require("gopher.config").commands
local r = require "gopher._utils.runner" local r = require "gopher._utils.runner"
local u = require "gopher._utils" local u = require "gopher._utils"
local log = require "gopher._utils.log"
local installer = {} local installer = {}
local urls = { local urls = {
gomodifytags = "github.com/fatih/gomodifytags", gomodifytags = "github.com/fatih/gomodifytags@latest",
impl = "github.com/josharian/impl", impl = "github.com/josharian/impl@latest",
gotests = "github.com/cweill/gotests/...", gotests = "github.com/cweill/gotests/...@develop",
iferr = "github.com/koron/iferr", iferr = "github.com/koron/iferr@latest",
dlv = "github.com/go-delve/delve/cmd/dlv",
} }
---@param pkg string ---@param opt vim.SystemCompleted
local function install(pkg) ---@param url string
local url = urls[pkg] .. "@latest" local function handle_intall_exit(opt, url)
r.sync(c.go, { if opt.code ~= 0 then
args = { "install", url }, vim.schedule(function()
on_exit = function(data, status) u.notify("go install failed: " .. url)
if not status == 0 then end)
error("go install failed: " .. data)
return log.error("go install failed:", "url", url, "opt", vim.inspect(opt))
end return
u.notify("installed: " .. url) end
end,
}) vim.schedule(function()
u.notify("go install-ed: " .. url)
end)
end
---@param url string
local function install(url)
r.async({ c.go, "install", url }, function(opt)
handle_intall_exit(opt, url)
end)
end
---@param url string
local function install_sync(url)
local rs = r.sync { c.go, "install", url }
handle_intall_exit(rs, url)
end end
---Install required go deps ---Install required go deps
function installer.install_deps() ---@param opts? {sync:boolean}
for pkg, _ in pairs(urls) do function installer.install_deps(opts)
install(pkg) opts = opts or {}
for _, url in pairs(urls) do
if opts.sync then
install_sync(url)
else
install(url)
end
end end
end end

View file

@ -1,13 +1,19 @@
---@toc_entry Modifty struct tags ---@toc_entry Modify struct tags
---@tag gopher.nvim-struct-tags ---@tag gopher.nvim-struct-tags
---@text struct-tags is utilizing the `gomodifytags` tool to add or remove tags to struct fields. ---@text
---@usage - put your coursor on the struct --- `struct_tags` is utilizing the `gomodifytags` tool to add or remove tags to struct fields.
--- - run `:GoTagAdd json` to add json tags to struct fields
--- - run `:GoTagRm json` to remove json tags to struct fields
--- ---
--- note: if you dont spesify the tag it will use `json` as default ---@usage
--- How to add/remove tags to struct fields:
--- 1. Place cursor on the struct
--- 2. Run `:GoTagAdd json` to add json tags to struct fields
--- 3. Run `:GoTagRm json` to remove json tags to struct fields
--- ---
--- simple example: --- To clear all tags from struct run: `:GoTagClear`
---
--- NOTE: if you dont specify the tag it will use `json` as default
---
--- Example:
--- >go --- >go
--- // before --- // before
--- type User struct { --- type User struct {
@ -24,105 +30,109 @@
--- } --- }
--- < --- <
local ts_utils = require "gopher._utils.ts" local ts = require "gopher._utils.ts"
local r = require "gopher._utils.runner" local r = require "gopher._utils.runner"
local c = require "gopher.config" local c = require "gopher.config"
local u = require "gopher._utils"
local log = require "gopher._utils.log"
local struct_tags = {} local struct_tags = {}
local function modify(...) ---@param fpath string
local fpath = vim.fn.expand "%" ---@diagnostic disable-line: missing-parameter ---@param bufnr integer
local ns = ts_utils.get_struct_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0))) ---@param user_args string[]
if ns == nil then ---@dochide
return local function handle_tags(fpath, bufnr, user_args)
end local st = ts.get_struct_under_cursor(bufnr)
-- stylua: ignore -- stylua: ignore
local cmd_args = { local cmd = {
c.commands.gomodifytags,
"-transform", c.gotag.transform, "-transform", c.gotag.transform,
"-format", "json", "-format", "json",
"-file", fpath, "-file", fpath,
"-w" "-w",
} }
-- by struct name of line pos if st.is_varstruct then
if ns.name == nil then table.insert(cmd, "-line")
local _, csrow, _, _ = unpack(vim.fn.getpos ".") table.insert(cmd, string.format("%d,%d", st.start_line, st.end_line))
table.insert(cmd_args, "-line")
table.insert(cmd_args, csrow)
else else
table.insert(cmd_args, "-struct") table.insert(cmd, "-struct")
table.insert(cmd_args, ns.name) table.insert(cmd, st.name)
end end
-- set user args for cmd for _, v in ipairs(user_args) do
local arg = { ... } table.insert(cmd, v)
for _, v in ipairs(arg) do
table.insert(cmd_args, v)
end end
-- set default tag for "clear tags" local rs = r.sync(cmd)
if #arg == 1 and arg[1] ~= "-clear-tags" then if rs.code ~= 0 then
table.insert(cmd_args, "json") log.error("tags: failed to set tags " .. rs.stderr)
error("failed to set tags " .. rs.stderr)
end end
local output = r.sync(c.commands.gomodifytags, { local res = vim.json.decode(rs.stdout)
args = cmd_args, if res["errors"] then
on_exit = function(data, status) log.error("tags: got an error " .. vim.inspect(res))
if not status == 0 then error("failed to set tags " .. vim.inspect(res["errors"]))
error("gotag failed: " .. data) end
end
end,
})
-- decode goted value for i, v in ipairs(res["lines"]) do
local tagged = vim.json.decode(table.concat(output)) res["lines"][i] = u.trimend(v)
if
tagged.errors ~= nil
or tagged.lines == nil
or tagged["start"] == nil
or tagged["start"] == 0
then
error("failed to set tags " .. vim.inspect(tagged))
end end
vim.api.nvim_buf_set_lines( vim.api.nvim_buf_set_lines(
0, bufnr,
tagged.start - 1, res["start"] - 1,
tagged.start - 1 + #tagged.lines, res["start"] - 1 + #res["lines"],
false, true,
tagged.lines res["lines"]
) )
vim.cmd "write"
end end
-- add tags to struct under cursor ---@param args string[]
---@return string
---@dochide
local function handler_user_args(args)
if #args == 0 then
return c.gotag.default_tag
end
return table.concat(args, ",")
end
-- Adds tags to a struct under the cursor
-- See |gopher.nvim-struct-tags|
---@param ... string Tags to add to the struct fields. If not provided, it will use [config.gotag.default_tag]
---@dochide
function struct_tags.add(...) function struct_tags.add(...)
local arg = { ... } local args = { ... }
if #arg == nil or arg == "" then local fpath = vim.fn.expand "%"
arg = { "json" } local bufnr = vim.api.nvim_get_current_buf()
end
local cmd_args = { "-add-tags" } local user_tags = handler_user_args(args)
for _, v in ipairs(arg) do handle_tags(fpath, bufnr, { "-add-tags", user_tags })
table.insert(cmd_args, v)
end
modify(unpack(cmd_args))
end end
-- remove tags to struct under cursor -- Removes tags from a struct under the cursor
-- See `:h gopher.nvim-struct-tags`
---@dochide
---@param ... string Tags to add to the struct fields. If not provided, it will use [config.gotag.default_tag]
function struct_tags.remove(...) function struct_tags.remove(...)
local arg = { ... } local args = { ... }
if #arg == nil or arg == "" then local fpath = vim.fn.expand "%"
arg = { "json" } local bufnr = vim.api.nvim_get_current_buf()
end
local cmd_args = { "-remove-tags" } local user_tags = handler_user_args(args)
for _, v in ipairs(arg) do handle_tags(fpath, bufnr, { "-remove-tags", user_tags })
table.insert(cmd_args, v) end
end
modify(unpack(cmd_args)) -- Removes all tags from a struct under the cursor
-- See `:h gopher.nvim-struct-tags`
---@dochide
function struct_tags.clear()
local fpath = vim.fn.expand "%"
local bufnr = vim.api.nvim_get_current_buf()
handle_tags(fpath, bufnr, { "-clear-tags" })
end end
return struct_tags return struct_tags

View file

@ -1,6 +1,9 @@
[vim] [vim]
any = true any = true
[MiniTest]
any = true
[describe] [describe]
any = true any = true
[[describe.args]] [[describe.args]]

13
pkg.json Normal file
View file

@ -0,0 +1,13 @@
{
"name": "gopher.nvim",
"engines": {
"nvim": "^0.10.0"
},
"repository": {
"type": "git",
"url": "https://github.com/olexsmir/gopher.nvim"
},
"dependencies": {
"https://github.com/nvim-treesitter/nvim-treesitter": "*"
}
}

87
plugin/gopher.lua Normal file
View file

@ -0,0 +1,87 @@
---@toc_entry Commands
---@tag gopher.nvim-commands
---@text
--- If don't want to automatically register plugins' commands,
--- you can set `vim.g.gopher_register_commands` to `false`, before loading the plugin.
if vim.g.gopher_register_commands == false then
return
end
---@param name string
---@param fn fun(args: table)
---@param nargs? number|"*"|"?"
---@private
local function cmd(name, fn, nargs)
nargs = nargs or 0
vim.api.nvim_create_user_command(name, fn, { nargs = nargs })
end
cmd("GopherLog", function()
vim.cmd("tabnew " .. require("gopher._utils.log").get_outfile())
end)
cmd("GoIfErr", function()
require("gopher").iferr()
end)
cmd("GoCmt", function()
require("gopher").comment()
end)
cmd("GoImpl", function(args)
require("gopher").impl(unpack(args.fargs))
end, "*")
-- :GoInstall
cmd("GoInstallDeps", function()
require("gopher").install_deps()
end)
cmd("GoInstallDepsSync", function()
require("gopher").install_deps { sync = true }
end)
-- :GoTag
cmd("GoTagAdd", function(opts)
require("gopher").tags.add(unpack(opts.fargs))
end, "*")
cmd("GoTagRm", function(opts)
require("gopher").tags.rm(unpack(opts.fargs))
end, "*")
cmd("GoTagClear", function()
require("gopher").tags.clear()
end)
-- :GoTest
cmd("GoTestAdd", function()
require("gopher").test.add()
end)
cmd("GoTestsAll", function()
require("gopher").test.all()
end)
cmd("GoTestsExp", function()
require("gopher").test.exported()
end)
-- :Go
cmd("GoMod", function(opts)
require("gopher").mod(opts.fargs)
end, "*")
cmd("GoGet", function(opts)
vim.print(opts)
require("gopher").get(opts.fargs)
end, "*")
cmd("GoWork", function(opts)
require("gopher").get(opts.fargs)
end, "*")
cmd("GoGenerate", function(opts)
require("gopher").generate(opts.fargs or "")
end, "?")

View file

@ -1,14 +0,0 @@
command! -nargs=* GoTagAdd :lua require"gopher".tags.add(<f-args>)
command! -nargs=* GoTagRm :lua require"gopher".tags.rm(<f-args>)
command! GoTestAdd :lua require"gopher".test.add()
command! GoTestsAll :lua require"gopher".test.all()
command! GoTestsExp :lua require"gopher".test.exported()
command! -nargs=* GoMod :lua require"gopher".mod(<f-args>)
command! -nargs=* GoGet :lua require"gopher".get(<f-args>)
command! -nargs=* GoWork :lua require"gopher".work(<f-args>)
command! -nargs=* GoImpl :lua require"gopher".impl(<f-args>)
command! -nargs=* GoGenerate :lua require"gopher".generate(<f-args>)
command! GoCmt :lua require"gopher".comment()
command! GoIfErr :lua require"gopher".iferr()
command! GoInstallDeps :lua require"gopher".install_deps()
command! GopherLog :lua vim.cmd("tabnew " .. require("gopher._utils.log").get_outfile())

View file

@ -10,12 +10,12 @@ end
local files = { local files = {
"lua/gopher/init.lua", "lua/gopher/init.lua",
"lua/gopher/config.lua", "lua/gopher/config.lua",
"plugin/gopher.lua",
"lua/gopher/struct_tags.lua", "lua/gopher/struct_tags.lua",
"lua/gopher/impl.lua", "lua/gopher/impl.lua",
"lua/gopher/gotests.lua", "lua/gopher/gotests.lua",
"lua/gopher/iferr.lua", "lua/gopher/iferr.lua",
"lua/gopher/comment.lua", "lua/gopher/comment.lua",
"lua/gopher/dap.lua",
} }
minidoc.setup() minidoc.setup()
@ -30,4 +30,8 @@ hooks.write_pre = function(lines)
return lines return lines
end end
hooks.sections["@dochide"] = function(s)
s.parent:clear_lines()
end
MiniDoc.generate(files, "doc/gopher.nvim.txt", { hooks = hooks }) MiniDoc.generate(files, "doc/gopher.nvim.txt", { hooks = hooks })

View file

@ -6,29 +6,58 @@ end
local function install_plug(plugin) local function install_plug(plugin)
local name = plugin:match ".*/(.*)" local name = plugin:match ".*/(.*)"
local package_root = root ".tests/site/pack/deps/start/" local package_root = root ".tests/site/pack/deps/start/"
if not vim.loop.fs_stat(package_root .. name) then if not vim.uv.fs_stat(package_root .. name) then
print("Installing " .. plugin) print("Installing " .. plugin)
vim.fn.mkdir(package_root, "p") vim
vim.fn.system { .system({
"git", "git",
"clone", "clone",
"--depth=1", "--depth=1",
"https://github.com/" .. plugin .. ".git", "https://github.com/" .. plugin .. ".git",
package_root .. "/" .. name, package_root .. "/" .. name,
} })
:wait()
end end
end end
vim.cmd [[set runtimepath=$VIMRUNTIME]]
vim.opt.runtimepath:append(root())
vim.opt.packpath = { root ".tests/site" }
vim.notify = print
install_plug "nvim-lua/plenary.nvim" install_plug "nvim-lua/plenary.nvim"
install_plug "nvim-treesitter/nvim-treesitter" install_plug "nvim-treesitter/nvim-treesitter"
install_plug "echasnovski/mini.doc" -- used for docs generation install_plug "echasnovski/mini.doc" -- used for docs generation
install_plug "echasnovski/mini.test"
vim.env.XDG_CONFIG_HOME = root ".tests/config" vim.env.XDG_CONFIG_HOME = root ".tests/config"
vim.env.XDG_DATA_HOME = root ".tests/data" vim.env.XDG_DATA_HOME = root ".tests/data"
vim.env.XDG_STATE_HOME = root ".tests/state" vim.env.XDG_STATE_HOME = root ".tests/state"
vim.env.XDG_CACHE_HOME = root ".tests/cache" vim.env.XDG_CACHE_HOME = root ".tests/cache"
vim.opt.runtimepath:append(root())
vim.opt.packpath:append(root ".tests/site")
vim.notify = vim.print
-- install go treesitter parse
require("nvim-treesitter.install").ensure_installed_sync "go"
require("gopher").setup {
log_level = vim.log.levels.OFF,
timeout = 4000,
}
-- setup mini.test only when running headless nvim
if #vim.api.nvim_list_uis() == 0 then
require("mini.test").setup {
collect = {
find_files = function()
return vim.fn.globpath("spec", "**/*_test.lua", true, true)
end,
},
}
end
-- needed for tests, i dont know the reason why, but on start
-- vim is not able to use treesitter for go by default
vim.api.nvim_create_autocmd("FileType", {
pattern = "go",
callback = function(args)
vim.treesitter.start(args.buf, "go")
end,
})

0
spec/fixtures/comment/empty_input.go vendored Normal file
View file

2
spec/fixtures/comment/empty_output.go vendored Normal file
View file

@ -0,0 +1,2 @@
//

5
spec/fixtures/comment/func_input.go vendored Normal file
View file

@ -0,0 +1,5 @@
package main
func Test(a int) bool {
return false
}

6
spec/fixtures/comment/func_output.go vendored Normal file
View file

@ -0,0 +1,6 @@
package main
// Test
func Test(a int) bool {
return false
}

View file

@ -0,0 +1,3 @@
package main
type Testinger interface{}

View file

@ -0,0 +1,4 @@
package main
// Testinger
type Testinger interface{}

7
spec/fixtures/comment/method_input.go vendored Normal file
View file

@ -0,0 +1,7 @@
package main
type Method struct{}
func (Method) Run() error {
return nil
}

View file

@ -0,0 +1,8 @@
package main
type Method struct{}
// Run
func (Method) Run() error {
return nil
}

View file

@ -0,0 +1 @@
package main

View file

@ -0,0 +1,2 @@
// Package main provides
package main

3
spec/fixtures/comment/struct_input.go vendored Normal file
View file

@ -0,0 +1,3 @@
package main
type CommentStruct struct{}

View file

@ -0,0 +1,4 @@
package main
// CommentStruct
type CommentStruct struct{}

9
spec/fixtures/iferr/iferr_input.go vendored Normal file
View file

@ -0,0 +1,9 @@
package main
func test() error {
return nil
}
func main() {
err := test()
}

12
spec/fixtures/iferr/iferr_output.go vendored Normal file
View file

@ -0,0 +1,12 @@
package main
func test() error {
return nil
}
func main() {
err := test()
if err != nil {
return
}
}

7
spec/fixtures/iferr/message_input.go vendored Normal file
View file

@ -0,0 +1,7 @@
package main
func getErr() error { return nil }
func test() error {
err := getErr()
}

10
spec/fixtures/iferr/message_output.go vendored Normal file
View file

@ -0,0 +1,10 @@
package main
func getErr() error { return nil }
func test() error {
err := getErr()
if err != nil {
return fmt.Errorf("failed to %w", err)
}
}

3
spec/fixtures/impl/closer_input.go vendored Normal file
View file

@ -0,0 +1,3 @@
package main
type CloserTest struct{}

7
spec/fixtures/impl/closer_output.go vendored Normal file
View file

@ -0,0 +1,7 @@
package main
type CloserTest2 struct{}
func (closertest *CloserTest2) Close() error {
panic("not implemented") // TODO: Implement
}

3
spec/fixtures/impl/reader_input.go vendored Normal file
View file

@ -0,0 +1,3 @@
package main
type Read struct{}

7
spec/fixtures/impl/reader_output.go vendored Normal file
View file

@ -0,0 +1,7 @@
package main
func (r *Read2) Read(p []byte) (n int, err error) {
panic("not implemented") // TODO: Implement
}
type Read2 struct{}

3
spec/fixtures/impl/writer_input.go vendored Normal file
View file

@ -0,0 +1,3 @@
package main
type WriterTest struct{}

7
spec/fixtures/impl/writer_output.go vendored Normal file
View file

@ -0,0 +1,7 @@
package main
type WriterTest2 struct{}
func (w *WriterTest2) Write(p []byte) (n int, err error) {
panic("not implemented") // TODO: Implement
}

11
spec/fixtures/tags/add_many_input.go vendored Normal file
View file

@ -0,0 +1,11 @@
package main
type Test struct {
ID int
Name string
Num int64
Another struct {
First int
Second string
}
}

11
spec/fixtures/tags/add_many_output.go vendored Normal file
View file

@ -0,0 +1,11 @@
package main
type Test struct {
ID int `test4:"id" test5:"id" test1:"id" test2:"id"`
Name string `test4:"name" test5:"name" test1:"name" test2:"name"`
Num int64 `test4:"num" test5:"num" test1:"num" test2:"num"`
Another struct {
First int `test4:"first" test5:"first" test1:"first" test2:"first"`
Second string `test4:"second" test5:"second" test1:"second" test2:"second"`
} `test4:"another" test5:"another" test1:"another" test2:"another"`
}

11
spec/fixtures/tags/clear_input.go vendored Normal file
View file

@ -0,0 +1,11 @@
package main
type Test struct {
ID int `json:"id" yaml:"id" xml:"id" db:"id"`
Name string `json:"name" yaml:"name" xml:"name" db:"name"`
Num int64 `json:"num" yaml:"num" xml:"num" db:"num"`
Another struct {
First int `json:"first" yaml:"first" xml:"first" db:"first"`
Second string `json:"second" yaml:"second" xml:"second" db:"second"`
} `json:"another" yaml:"another" xml:"another" db:"another"`
}

11
spec/fixtures/tags/clear_output.go vendored Normal file
View file

@ -0,0 +1,11 @@
package main
type Test struct {
ID int
Name string
Num int64
Another struct {
First int
Second string
}
}

18
spec/fixtures/tags/many_input.go vendored Normal file
View file

@ -0,0 +1,18 @@
package main
type (
TestOne struct {
Asdf string
ID int
}
TestTwo struct {
Fesa int
A bool
}
TestThree struct {
Asufj int
Fs string
}
)

18
spec/fixtures/tags/many_output.go vendored Normal file
View file

@ -0,0 +1,18 @@
package main
type (
TestOne struct {
Asdf string
ID int
}
TestTwo struct {
Fesa int `testing:"fesa"`
A bool `testing:"a"`
}
TestThree struct {
Asufj int
Fs string
}
)

11
spec/fixtures/tags/svar_input.go vendored Normal file
View file

@ -0,0 +1,11 @@
package main
func main() {
s := struct {
API string
Key string
}{
API: "api.com",
Key: "key",
}
}

11
spec/fixtures/tags/svar_output.go vendored Normal file
View file

@ -0,0 +1,11 @@
package main
func main() {
s := struct {
API string `xml:"api"`
Key string `xml:"key"`
}{
API: "api.com",
Key: "key",
}
}

8
spec/fixtures/tags/var_input.go vendored Normal file
View file

@ -0,0 +1,8 @@
package main
func main() {
var a struct {
TestField1 string
TestField2 string
}
}

8
spec/fixtures/tags/var_output.go vendored Normal file
View file

@ -0,0 +1,8 @@
package main
func main() {
var a struct {
TestField1 string `yaml:"test_field_1"`
TestField2 string `yaml:"test_field_2"`
}
}

5
spec/fixtures/tests/function_input.go vendored Normal file
View file

@ -0,0 +1,5 @@
package fortest
func Add(x, y int) int {
return 2 + x + y
}

24
spec/fixtures/tests/function_output.go vendored Normal file
View file

@ -0,0 +1,24 @@
package fortest
import "testing"
func TestAdd(t *testing.T) {
type args struct {
x int
y int
}
tests := []struct {
name string
args args
want int
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Add(tt.args.x, tt.args.y); got != tt.want {
t.Errorf("Add() = %v, want %v", got, tt.want)
}
})
}
}

7
spec/fixtures/tests/method_input.go vendored Normal file
View file

@ -0,0 +1,7 @@
package fortest
type ForTest struct{}
func (t *ForTest) Add(x, y int) int {
return 2 + x + y
}

26
spec/fixtures/tests/method_output.go vendored Normal file
View file

@ -0,0 +1,26 @@
package fortest
import "testing"
func TestForTest_Add(t *testing.T) {
type args struct {
x int
y int
}
tests := []struct {
name string
tr *ForTest
args args
want int
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tr := &ForTest{}
if got := tr.Add(tt.args.x, tt.args.y); got != tt.want {
t.Errorf("ForTest.Add() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -0,0 +1,37 @@
local t = require "spec.testutils"
local child, T = t.setup "comment"
local function do_the_test(fixture, pos)
local rs = t.setup_test("comment/" .. fixture, child, pos)
child.cmd "GoCmt"
child.cmd "write"
t.eq(t.readfile(rs.tmp), rs.fixtures.output)
t.cleanup(rs)
end
T["comment"]["should add comment to package"] = function()
do_the_test("package", { 1, 1 })
end
T["comment"]["should add comment to struct"] = function()
do_the_test("struct", { 4, 1 })
end
T["comment"]["should add comment to function"] = function()
do_the_test("func", { 3, 1 })
end
T["comment"]["should add comment to method"] = function()
do_the_test("method", { 5, 1 })
end
T["comment"]["should add comment to interface"] = function()
do_the_test("interface", { 3, 6 })
end
T["comment"]["otherwise should add // above cursor"] = function()
do_the_test("empty", { 1, 1 })
end
return T

View file

@ -0,0 +1,29 @@
local t = require "spec.testutils"
local child, T = t.setup "gotests"
--- NOTE: :GoTestAdd is the only place that has actual logic
--- All other parts are handled `gotests` itself.
---@param fpath string
---@return string
local function read_testfile(fpath)
return t.readfile(fpath:gsub(".go", "_test.go"))
end
T["gotests"]["should add test for function under cursor"] = function()
local rs = t.setup_test("tests/function", child, { 3, 5 })
child.cmd "GoTestAdd"
t.eq(rs.fixtures.output, read_testfile(rs.tmp))
t.cleanup(rs)
end
T["gotests"]["should add test for method under cursor"] = function()
local rs = t.setup_test("tests/method", child, { 5, 19 })
child.cmd "GoTestAdd"
t.eq(rs.fixtures.output, read_testfile(rs.tmp))
t.cleanup(rs)
end
return T

View file

@ -0,0 +1,27 @@
local t = require "spec.testutils"
local child, T = t.setup "iferr"
T["iferr"]["should add if != nil {"] = function()
local rs = t.setup_test("iferr/iferr", child, { 8, 2 })
child.cmd "GoIfErr"
child.cmd "write"
t.eq(t.readfile(rs.tmp), rs.fixtures.output)
t.cleanup(rs)
end
T["iferr"]["should add if err with custom message"] = function()
child.lua [[
require("gopher").setup {
iferr = { message = 'fmt.Errorf("failed to %w", err)' }
} ]]
local rs = t.setup_test("iferr/message", child, { 6, 2 })
child.cmd "GoIfErr"
child.cmd "write"
t.eq(t.readfile(rs.tmp), rs.fixtures.output)
t.cleanup(rs)
end
return T

View file

@ -0,0 +1,35 @@
local t = require "spec.testutils"
local child, T = t.setup "impl"
T["impl"]["should do impl with 'w io.Writer'"] = function()
local rs = t.setup_test("impl/writer", child, { 3, 0 })
child.cmd "GoImpl w io.Writer"
child.cmd "write"
-- NOTE: since "impl" won't implement interface if it's already implemented i went with this hack
local rhs = rs.fixtures.output:gsub("Test2", "Test")
t.eq(t.readfile(rs.tmp), rhs)
t.cleanup(rs)
end
T["impl"]["should work with full input, 'r Read io.Reader'"] = function()
local rs = t.setup_test("impl/reader", child)
child.cmd "GoImpl r Read io.Reader"
child.cmd "write"
local rhs = rs.fixtures.output:gsub("Read2", "Read")
t.eq(t.readfile(rs.tmp), rhs)
t.cleanup(rs)
end
T["impl"]["should work with minimal input 'io.Closer'"] = function()
local rs = t.setup_test("impl/closer", child, { 3, 6 })
child.cmd "GoImpl io.Closer"
child.cmd "write"
local rhs = rs.fixtures.output:gsub("Test2", "Test")
t.eq(t.readfile(rs.tmp), rhs)
t.cleanup(rs)
end
return T

View file

@ -0,0 +1,81 @@
local t = require "spec.testutils"
local child, T = t.setup "struct_tags"
T["struct_tags"]["should add tag"] = function()
local rs = t.setup_test("tags/add", child, { 3, 6 })
child.cmd "GoTagAdd json"
child.cmd "write"
t.eq(t.readfile(rs.tmp), rs.fixtures.output)
t.cleanup(rs)
end
T["struct_tags"]["should remove tag"] = function()
local rs = t.setup_test("tags/remove", child, { 4, 6 })
child.cmd "GoTagRm json"
child.cmd "write"
t.eq(t.readfile(rs.tmp), rs.fixtures.output)
t.cleanup(rs)
end
T["struct_tags"]["should be able to handle many structs"] = function()
local rs = t.setup_test("tags/many", child, { 10, 3 })
child.cmd "GoTagAdd testing"
child.cmd "write"
t.eq(t.readfile(rs.tmp), rs.fixtures.output)
t.cleanup(rs)
end
T["struct_tags"]["should clear struct"] = function()
local rs = t.setup_test("tags/clear", child, { 3, 1 })
child.cmd "GoTagClear"
child.cmd "write"
t.eq(t.readfile(rs.tmp), rs.fixtures.output)
t.cleanup(rs)
end
T["struct_tags"]["should add more than one tag"] = function()
local tmp = t.tmpfile()
local fixtures = t.get_fixtures "tags/add_many"
t.writefile(tmp, fixtures.input)
--- with comma, like gomodifytags
child.cmd("silent edit " .. tmp)
child.fn.setpos(".", { child.fn.bufnr(tmp), 3, 1 })
child.cmd "GoTagAdd test4,test5"
child.cmd "write"
-- without comma
child.cmd("silent edit " .. tmp)
child.fn.setpos(".", { child.fn.bufnr(tmp), 3, 1 })
child.cmd "GoTagAdd test1 test2"
child.cmd "write"
t.eq(t.readfile(tmp), fixtures.output)
---@diagnostic disable-next-line:missing-fields
t.cleanup { tmp = tmp }
end
T["struct_tags"]["should add tags on var"] = function()
local rs = t.setup_test("tags/var", child, { 5, 6 })
child.cmd "GoTagAdd yaml"
child.cmd "write"
t.eq(t.readfile(rs.tmp), rs.fixtures.output)
t.cleanup(rs)
end
T["struct_tags"]["should add tags on short declr var"] = function()
local rs = t.setup_test("tags/svar", child, { 4, 3 })
child.cmd "GoTagAdd xml"
child.cmd "write"
t.eq(t.readfile(rs.tmp), rs.fixtures.output)
t.cleanup(rs)
end
return T

102
spec/testutils.lua Normal file
View file

@ -0,0 +1,102 @@
local base_dir = vim.env.GOPHER_DIR or vim.fn.expand "%:p:h"
---@class gopher.TestUtils
local testutils = {}
testutils.mininit_path = vim.fs.joinpath(base_dir, "scripts", "minimal_init.lua")
testutils.fixtures_dir = vim.fs.joinpath(base_dir, "spec/fixtures")
---@param name string
---@return MiniTest.child, table
function testutils.setup(name)
local child = MiniTest.new_child_neovim()
local T = MiniTest.new_set {
hooks = {
post_once = child.stop,
pre_case = function()
child.restart { "-u", testutils.mininit_path }
end,
},
}
T[name] = MiniTest.new_set {}
return child, T
end
---@generic T
---@param a T
---@param b T
---@return boolean
function testutils.eq(a, b)
return MiniTest.expect.equality(a, b)
end
---@return string
function testutils.tmpfile()
return vim.fn.tempname() .. ".go"
end
---@param path string
---@return string
function testutils.readfile(path)
return vim.fn.join(vim.fn.readfile(path), "\n")
end
---@param fpath string
---@param contents string
function testutils.writefile(fpath, contents)
vim.fn.writefile(vim.split(contents, "\n"), fpath)
end
---@param fpath string
function testutils.deletefile(fpath)
vim.fn.delete(fpath)
end
---@class gopher.TestUtilsFixtures
---@field input string
---@field output string
---@param fixture string
---@return gopher.TestUtilsFixtures
function testutils.get_fixtures(fixture)
return {
input = testutils.readfile(vim.fs.joinpath(testutils.fixtures_dir, fixture) .. "_input.go"),
output = testutils.readfile(vim.fs.joinpath(testutils.fixtures_dir, fixture) .. "_output.go"),
}
end
---@class gopher.TestUtilsSetup
---@field tmp string
---@field fixtures gopher.TestUtilsFixtures
---@field bufnr number
---@param fixture string
---@param child MiniTest.child
---@param pos? number[]
---@return gopher.TestUtilsSetup
function testutils.setup_test(fixture, child, pos)
local tmp = testutils.tmpfile()
local fixtures = testutils.get_fixtures(fixture)
testutils.writefile(tmp, fixtures.input)
child.cmd("silent edit " .. tmp)
local bufnr = child.fn.bufnr(tmp)
if pos then
child.fn.setpos(".", { bufnr, unpack(pos) })
end
return {
tmp = tmp,
bufnr = bufnr,
fixtures = fixtures,
}
end
---@param inp gopher.TestUtilsSetup
function testutils.cleanup(inp)
testutils.deletefile(inp.tmp)
end
return testutils

25
spec/unit/utils_test.lua Normal file
View file

@ -0,0 +1,25 @@
local t = require "spec.testutils"
local _, T = t.setup "utils"
T["utils"]["should .remove_empty_lines()"] = function()
local u = require "gopher._utils"
local inp = { "hi", "", "a", "", "", "asdf" }
t.eq(u.remove_empty_lines(inp), { "hi", "a", "asdf" })
end
T["utils"]["should .readfile_joined()"] = function()
local data = "line1\nline2\nline3"
local tmp = t.tmpfile()
local u = require "gopher._utils"
t.writefile(tmp, data)
t.eq(u.readfile_joined(tmp), data)
end
T["utils"]["should .trimend()"] = function()
local u = require "gopher._utils"
t.eq(u.trimend " hi ", " hi")
end
return T

View file

@ -1,29 +0,0 @@
describe("gopher.config", function()
it(".setup() should provide default when .setup() is not called", function()
local c = require "gopher.config"
assert.are.same(c.commands.go, "go")
assert.are.same(c.commands.gomodifytags, "gomodifytags")
assert.are.same(c.commands.gotests, "gotests")
assert.are.same(c.commands.impl, "impl")
assert.are.same(c.commands.iferr, "iferr")
assert.are.same(c.commands.dlv, "dlv")
end)
it(".setup() should change options on users config", function()
local c = require "gopher.config"
c.setup {
commands = {
go = "go1.420",
gomodifytags = "iDontUseRustBtw",
},
}
assert.are.same(c.commands.go, "go1.420")
assert.are.same(c.commands.gomodifytags, "iDontUseRustBtw")
assert.are.same(c.commands.gotests, "gotests")
assert.are.same(c.commands.impl, "impl")
assert.are.same(c.commands.iferr, "iferr")
assert.are.same(c.commands.dlv, "dlv")
end)
end)

View file

@ -1,15 +0,0 @@
describe("gopher._utils", function()
local u = require "gopher._utils"
describe(".sreq()", function()
it("can require existing module", function()
assert.are.same(require "gopher", u.sreq "gopher")
end)
it("cannot require non-existing module", function()
assert.has.errors(function()
u.sreq "iDontExistBtw"
end)
end)
end)
end)