[mirror] Make your go dev experience better github.com/olexsmir/gopher.nvim
neovim golang

Compare changes

Choose any two refs to compare.

Changed files
+3677 -996
.github
autoload
health
doc
lua
plugin
scripts
spec
fixtures
integration
unit
vhs
+16
.editorconfig
··· 1 + root = true 2 + 3 + [*] 4 + indent_style = space 5 + indent_size = 4 6 + end_of_line = lf 7 + insert_final_newline = true 8 + trim_trailing_whitespace = true 9 + charset = utf-8 10 + 11 + [*.{md,yml,yaml,toml,lua,vim}] 12 + indent_size = 2 13 + 14 + [*.go] 15 + indent_style = tab 16 + indent_size = 4
+4
.envrc
··· 1 + dotenv 2 + 3 + # GOPHER_DIR - needed only for tests, to find the root of the project 4 + env_vars_required GOPHER_DIR
-23
.github/workflows/ci.yml
··· 1 - name: Format and lint 2 - on: [push, pull_request] 3 - 4 - jobs: 5 - format: 6 - name: stylua 7 - runs-on: ubuntu-latest 8 - steps: 9 - - uses: actions/checkout@v2 10 - - uses: JohnnyMorganz/stylua-action@1.0.0 11 - with: 12 - token: ${{ secrets.GITHUB_TOKEN }} 13 - args: --check . 14 - 15 - lint: 16 - name: selene 17 - runs-on: ubuntu-latest 18 - steps: 19 - - uses: actions/checkout@v2 20 - - uses: NTBBloodbath/selene-action@v1.0.0 21 - with: 22 - token: ${{ secrets.GITHUB_TOKEN }} 23 - args: --display-style=quiet .
+58
.github/workflows/linters.yml
··· 1 + name: linters 2 + 3 + on: 4 + push: 5 + branches: 6 + - main 7 + - develop 8 + pull_request: 9 + 10 + jobs: 11 + linters: 12 + name: Lua 13 + runs-on: ubuntu-latest 14 + steps: 15 + - uses: actions/checkout@v4 16 + - uses: JohnnyMorganz/stylua-action@v3 17 + with: 18 + token: ${{ secrets.GITHUB_TOKEN }} 19 + version: latest 20 + args: --check . 21 + 22 + - uses: NTBBloodbath/selene-action@v1.0.0 23 + with: 24 + token: ${{ secrets.GITHUB_TOKEN }} 25 + args: . 26 + 27 + docs: 28 + name: Docs 29 + runs-on: ubuntu-latest 30 + steps: 31 + - uses: actions/checkout@v4 32 + 33 + - name: Install Task 34 + uses: arduino/setup-task@v1 35 + with: 36 + version: 3.x 37 + repo-token: ${{ secrets.GITHUB_TOKEN }} 38 + 39 + - name: Install NeoVim 40 + uses: rhysd/action-setup-vim@v1 41 + with: 42 + neovim: true 43 + version: stable 44 + 45 + - name: Cache .tests 46 + uses: actions/cache@v4 47 + with: 48 + path: | 49 + ${{ github.workspace }}/.tests 50 + key: ${{ runner.os }}-tests-${{ hashFiles('${{ github.workspace }}/.tests') }} 51 + 52 + - name: Generate docs 53 + run: task docgen 54 + 55 + - name: Diff 56 + run: | 57 + git diff doc 58 + exit $(git status --porcelain doc | wc -l | tr -d " ")
+62
.github/workflows/tests.yml
··· 1 + name: tests 2 + 3 + on: 4 + push: 5 + branches: 6 + - main 7 + - develop 8 + pull_request: 9 + 10 + jobs: 11 + tests: 12 + strategy: 13 + matrix: 14 + os: [ubuntu-latest] 15 + version: 16 + - stable 17 + - nightly 18 + - v0.10.0 19 + - v0.10.4 20 + - v0.11.0 21 + - v0.11.1 22 + - v0.11.2 23 + - v0.11.3 24 + - v0.11.4 25 + runs-on: ${{ matrix.os }} 26 + steps: 27 + - name: Install Task 28 + uses: arduino/setup-task@v1 29 + with: 30 + version: 3.x 31 + repo-token: ${{ secrets.GITHUB_TOKEN }} 32 + 33 + - name: Install Go 34 + uses: actions/setup-go@v5 35 + with: 36 + go-version: "1.24.0" 37 + check-latest: false 38 + 39 + - name: Install NeoVim 40 + uses: rhysd/action-setup-vim@v1 41 + with: 42 + neovim: true 43 + version: ${{ matrix.version }} 44 + 45 + - uses: actions/checkout@v4 46 + 47 + - name: Cache .tests 48 + uses: actions/cache@v4 49 + with: 50 + path: | 51 + ${{ github.workspace }}/.tests 52 + ~/.cache/go-build 53 + ~/go/pkg/mod 54 + key: ${{ runner.os }}-tests-${{ hashFiles('${{ github.workspace }}/.tests') }} 55 + 56 + - name: Install Go bins 57 + run: task install-deps 58 + 59 + - name: Run Tests 60 + run: | 61 + nvim --version 62 + task test
+3 -1
.gitignore
··· 1 - playground/ 1 + /playground/ 2 + /.tests/ 3 + /.env
-15
.pre-commit-config.yaml
··· 1 - repos: 2 - - repo: local 3 - hooks: 4 - - id: stylua 5 - name: StyLua 6 - language: rust 7 - entry: stylua 8 - types: [lua] 9 - args: ["--check", "-"] 10 - - id: selene 11 - name: Selene 12 - language: rust 13 - entry: selene 14 - types: [lua] 15 - args: ["-"]
+49
CONTRIBUTING.md
··· 1 + # Contributing to `gopher.nvim` 2 + Thank you for taking the time to submit some code to gopher.nvim. It means a lot! 3 + 4 + ### Task running 5 + In this codebase for running tasks is used [Taskfile](https://taskfile.dev). 6 + You can install it with: 7 + ```bash 8 + go install github.com/go-task/task/v3/cmd/task@latest 9 + ``` 10 + 11 + ### Formatting and linting 12 + Code is formatted by [stylua](https://github.com/JohnnyMorganz/StyLua) and linted using [selene](https://github.com/Kampfkarren/selene). 13 + You can install these with: 14 + 15 + ```bash 16 + sudo pacman -S selene stylua 17 + # or whatever is your package manager 18 + ``` 19 + 20 + For formatting use this following commands, or setup your editor to integrate with selene/stylua: 21 + ```bash 22 + task format 23 + task lint 24 + ``` 25 + 26 + ### Documentation 27 + Here we're using [mini.doc](https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-doc.md) 28 + for generating vimhelp files based on [LuaCats](https://luals.github.io/wiki/annotations/) annotations in comments. 29 + 30 + For demo gifs in [readme](./README.md) we're using [vhs](https://github.com/charmbracelet/vhs). 31 + All files related to demos live in [/vhs](./vhs) dir. 32 + 33 + You can generate docs with: 34 + ```bash 35 + task docgen # generates vimhelp 36 + task vhs:generate # generates demo gifs 37 + ``` 38 + 39 + ### Commit messages 40 + We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), please follow it. 41 + 42 + ### Testing 43 + For testing this plugins uses [mini.test](https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-test.md). 44 + All tests live in [/spec](./spec) dir. 45 + 46 + You can run tests with: 47 + ```bash 48 + task test 49 + ```
+21
LICENSE
··· 1 + MIT License 2 + 3 + Copyright (c) 2025 Oleksandr Smirnov 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
-11
Makefile
··· 1 - .PHONY: 2 - .SILENT: 3 - 4 - format: 5 - stylua **/*.lua 6 - 7 - lint: 8 - selene **/*.lua 9 - 10 - test: 11 - nvim --headless -u ./spec/minimal.vim -c "PlenaryBustedDirectory spec {minimal_init='./spec/minimal.vim'}"
+244 -89
README.md
··· 1 1 # gopher.nvim 2 2 3 + [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct-single.svg)](https://stand-with-ukraine.pp.ua) 4 + 3 5 Minimalistic plugin for Go development in Neovim written in Lua. 4 6 5 - It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim. 7 + It's **NOT** an LSP tool, the goal of this plugin is to add go tooling support in Neovim. 8 + 9 + > All development of new and maybe undocumented, and unstable features is happening on [develop](https://github.com/olexsmir/gopher.nvim/tree/develop) branch. 10 + 11 + ## Table of content 12 + * [How to install](#install-using-lazynvim) 13 + * [Features](#features) 14 + * [Configuration](#configuration) 15 + * [Troubleshooting](#troubleshooting) 16 + * [Contributing](#contributing) 6 17 7 - ## Install 18 + ## Install (using [lazy.nvim](https://github.com/folke/lazy.nvim)) 8 19 9 - Pre-dependency: [go](https://github.com/golang/go) (tested on 1.17 and 1.18) 20 + Requirements: 21 + 22 + - **Neovim 0.10** or later 23 + - Treesitter parser for `go`(`:TSInstall go` if you use [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter)) 24 + - [Go](https://github.com/golang/go) installed 25 + 26 + > [!IMPORTANT] 27 + > If you prefer using other forges, this repository is also mirrored at: 28 + > - [tangled.org](https://tangled.org): [`https://tangled.org/olexsmir.xyz/gopher.nvim`](https://tangled.org/olexsmir.xyz/gopher.nvim) 29 + > - [codeberg.org](https://codeberg.org): [`https://codeberg.org/olexsmir/gopher.nvim`](https://codeberg.org/olexsmir/gopher.nvim) 10 30 11 31 ```lua 12 - use { 32 + -- NOTE: this plugin is already lazy-loaded and adds only about 1ms 33 + -- of load time to your config 34 + { 13 35 "olexsmir/gopher.nvim", 14 - requires = { -- dependencies 15 - "nvim-lua/plenary.nvim", 16 - "nvim-treesitter/nvim-treesitter", 17 - }, 36 + ft = "go", 37 + -- branch = "develop" 38 + -- (optional) updates the plugin's dependencies on each update 39 + build = function() 40 + vim.cmd.GoInstallDeps() 41 + end, 42 + ---@module "gopher" 43 + ---@type gopher.Config 44 + opts = {}, 18 45 } 19 46 ``` 20 47 21 - Also, run `TSInstall go` if `go` parser if isn't installed yet. 48 + ## Features 49 + 50 + <details> 51 + <summary> 52 + <b>Install plugin's go deps</b> 53 + </summary> 54 + 55 + ```vim 56 + :GoInstallDeps 57 + ``` 58 + 59 + This will install the following tools: 60 + 61 + - [gomodifytags](https://github.com/fatih/gomodifytags) 62 + - [impl](https://github.com/josharian/impl) 63 + - [gotests](https://github.com/cweill/gotests) 64 + - [iferr](https://github.com/koron/iferr) 65 + - [json2go](https://github.com/olexsmir/json2go) 66 + </details> 67 + 68 + <details> 69 + <summary> 70 + <b>Add and remove tags for structs via <a href="https://github.com/fatih/gomodifytags">gomodifytags</a></b> 71 + </summary> 72 + 73 + ![Add tags demo](./vhs/tags.gif) 74 + 75 + By default `json` tag will be added/removed, if not set: 76 + 77 + ```vim 78 + " add json tag 79 + :GoTagAdd json 80 + 81 + " add json tag with omitempty option 82 + :GoTagAdd json=omitempty 83 + 84 + " remove yaml tag 85 + :GoTagRm yaml 86 + ``` 87 + 88 + ```lua 89 + -- or you can use lua api 90 + require("gopher").tags.add "xml" 91 + require("gopher").tags.rm "proto" 92 + ``` 93 + </details> 94 + 95 + <details> 96 + <summary> 97 + <b>Generating tests via <a href="https://github.com/cweill/gotests">gotests</a></b> 98 + </summary> 99 + 100 + ```vim 101 + " Generate one test for a specific function/method(one under cursor) 102 + :GoTestAdd 103 + 104 + " Generate all tests for all functions/methods in the current file 105 + :GoTestsAll 106 + 107 + " Generate tests for only exported functions/methods in the current file 108 + :GoTestsExp 109 + ``` 110 + 111 + ```lua 112 + -- or you can use lua api 113 + require("gopher").test.add() 114 + require("gopher").test.exported() 115 + require("gopher").test.all() 116 + ``` 117 + 118 + For named tests see `:h gopher.nvim-gotests-named` 119 + </details> 120 + 121 + <details> 122 + <summary> 123 + <b>Run commands like <code>go mod/get/etc</code> inside of nvim</b> 124 + </summary> 125 + 126 + ```vim 127 + :GoGet github.com/gorilla/mux 128 + 129 + " Link can have an `http` or `https` prefix. 130 + :GoGet https://github.com/lib/pq 22 131 23 - ## Config 132 + " You can provide more than one package url 133 + :GoGet github.com/jackc/pgx/v5 github.com/google/uuid/ 24 134 25 - By `.setup` function you can configure the plugin. 135 + " go mod commands 136 + :GoMod tidy 137 + :GoMod init new-shiny-project 26 138 27 - Note: 139 + " go work commands 140 + :GoWork sync 28 141 29 - - Installer does not install the tool in user set path 142 + " run go generate in cwd 143 + :GoGenerate 30 144 31 - ```lua 32 - require("gopher").setup { 33 - commands = { 34 - go = "go", 35 - gomodifytags = "gomodifytags", 36 - gotests = "~/go/bin/gotests", -- also you can set custom command path 37 - impl = "impl", 38 - }, 39 - } 40 - ``` 145 + " run go generate for the current file 146 + :GoGenerate % 147 + ``` 148 + </details> 41 149 42 - ## Features 150 + <details> 151 + <summary> 152 + <b>Interface implementation via <a href="https://github.com/josharian/impl">impl<a></b> 153 + </summary> 43 154 44 - 1. Installation requires this go tool: 155 + ![Auto interface implementation demo](./vhs/impl.gif) 45 156 46 - ```vim 47 - :GoInstallDeps 48 - ``` 157 + Syntax of the command: 158 + ```vim 159 + :GoImpl [receiver] [interface] 49 160 50 - It will install next tools: 161 + " also you can put a cursor on the struct and run 162 + :GoImpl [interface] 163 + ``` 51 164 52 - - [gomodifytags](https://github.com/fatih/gomodifytags) 53 - - [impl](https://github.com/josharian/impl) 54 - - [gotests](https://github.com/cweill/gotests) 165 + Usage examples: 166 + ```vim 167 + :GoImpl r Read io.Reader 168 + :GoImpl Write io.Writer 55 169 56 - 2. Modify struct tags: 57 - By default `json` tag will be added/removed, if not set: 170 + " or you can simply put a cursor on the struct and run 171 + :GoImpl io.Reader 172 + ``` 173 + </details> 58 174 59 - ```vim 60 - :GoTagAdd json " For add json tag 61 - :GoTagRm yaml " For remove yaml tag 62 - ``` 175 + <details> 176 + <summary> 177 + <b>Generate boilerplate for doc comments</b> 178 + </summary> 63 179 64 - 3. Run `go mod` command: 180 + ![Generate comments](./vhs/comment.gif) 65 181 66 - ```vim 67 - :GoMod tidy " Runs `go mod tidy` 68 - :GoMod init asdf " Runs `go mod init asdf` 69 - ``` 182 + First set a cursor on **public** package/function/interface/struct and execute: 70 183 71 - 4. Run `go get` command 184 + ```vim 185 + :GoCmt 186 + ``` 187 + </details> 72 188 73 - Link can have a `http` or `https` prefix. 189 + <details> 190 + <summary> 191 + <b>Convert json to Go types</b> 192 + </summary> 74 193 75 - You can provide more than one package url: 194 + ![Convert JSON to Go types](./vhs/json2go.gif) 76 195 77 - ```vim 78 - :GoGet github.com/gorilla/mux 79 - ``` 196 + `:GoJson` opens a temporary buffer where you can paste or write JSON. 197 + Saving the buffer (`:w` or `:wq`) automatically closes it and inserts the generated Go struct into the original buffer at the cursor position. 80 198 81 - 5. Interface implementation 199 + Alternatively, you can pass JSON directly as an argument: 200 + ```vim 201 + :GoJson {"name": "Alice", "age": 30} 202 + ``` 82 203 83 - Command syntax: 204 + Additionally, `gopher.json2go` provides lua api, see `:h gopher.nvim-json2go` for details. 205 + </details> 84 206 85 - ```vim 86 - :GoImpl [receiver] [interface] 87 207 88 - " Also you can put cursor on the struct and run: 89 - :GoImpl [interface] 90 - ``` 208 + <details> 209 + <summary> 210 + <b>Generate <code>if err != nil {</code> via <a href="https://github.com/koron/iferr">iferr</a></b> 211 + </summary> 91 212 92 - Example of usage: 213 + ![Generate if err != nil {](./vhs/iferr.gif) 93 214 94 - ```vim 95 - " Example 96 - :GoImpl r Read io.Reader 97 - " or simply put your cursor in the struct and run: 98 - :GoImpl io.Reader 99 - ``` 215 + Set the cursor on the line with `err` and execute 100 216 101 - 6. Generate tests with [gotests](https://github.com/cweill/gotests) 217 + ```vim 218 + :GoIfErr 219 + ``` 220 + </details> 102 221 103 - Generate one test for spesific function/method: 222 + ## Configuration 104 223 105 - ```vim 106 - :GoTestAdd 107 - ``` 224 + > [!IMPORTANT] 225 + > 226 + > If you need more info look `:h gopher.nvim` 108 227 109 - Generate all tests for all functions/methods in current file: 228 + **Take a look at default options (might be a bit outdated, look `:h gopher.nvim-config`)** 110 229 111 - ```vim 112 - :GoTestsAll 113 - ``` 230 + ```lua 231 + require("gopher").setup { 232 + -- log level, you might consider using DEBUG or TRACE for debugging the plugin 233 + log_level = vim.log.levels.INFO, 114 234 115 - Generate tests only for exported functions/methods in current file: 235 + -- timeout for running internal commands 236 + timeout = 2000, 116 237 117 - ```vim 118 - :GoTestsExp 119 - ``` 238 + -- timeout for running installer commands(e.g :GoDepsInstall, :GoDepsInstallSync) 239 + installer_timeout = 999999, 120 240 121 - 7. Run `go generate` command; 241 + -- user specified paths to binaries 242 + commands = { 243 + go = "go", 244 + gomodifytags = "gomodifytags", 245 + gotests = "gotests", 246 + impl = "impl", 247 + iferr = "iferr", 248 + }, 249 + gotests = { 250 + -- a default template that gotess will use. 251 + -- gotets doesn't have template named `default`, we use it to represent absence of the provided template. 252 + template = "default", 122 253 123 - ```vim 124 - " Run `go generate` in cwd path 125 - :GoGenerate 254 + -- path to a directory containing custom test code templates 255 + template_dir = nil, 126 256 127 - " Run `go generate` for current file 128 - :GoGenerate % 129 - ``` 257 + -- use named tests(map with test name as key) in table tests(slice of structs by default) 258 + named = false, 259 + }, 260 + gotag = { 261 + transform = "snakecase", 130 262 131 - 8. Generate doc comment 263 + -- default tags to add to struct fields 264 + default_tag = "json", 132 265 133 - First set a cursor on **public** package/function/interface/struct and execure: 266 + -- default tag option added struct fields, set to nil to disable 267 + -- e.g: `option = "json=omitempty,xml=omitempty` 268 + option = nil, 269 + }, 270 + iferr = { 271 + -- choose a custom error message, nil to use default 272 + -- e.g: `message = 'fmt.Errorf("failed to %w", err)'` 273 + message = nil, 274 + }, 275 + json2go = { 276 + -- command used to open interactive input. 277 + -- e.g: `split`, `botright split`, `tabnew` 278 + interactive_cmd = "vsplit", 134 279 135 - ```vim 136 - :GoCmt 280 + -- name of autogenerated struct 281 + -- e.g: "MySuperCoolName" 282 + type_name = nil, 283 + }, 284 + } 137 285 ``` 138 286 139 - ## Thanks: 287 + ## Troubleshooting 288 + The most common issue with the plugin is missing dependencies. 289 + Run `:checkhealth gopher` to verify that the plugin is installed correctly. 290 + If any binaries are missing, install them using `:GoInstallDeps`. 140 291 141 - - [go.nvim](https://github.com/ray-x/go.nvim) 292 + If the issue persists, feel free to [open a new issue](https://github.com/olexsmir/gopher.nvim/issues/new). 293 + 294 + ## Contributing 295 + 296 + PRs are always welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md)
+42
Taskfile.yml
··· 1 + version: "3" 2 + 3 + includes: 4 + vhs: 5 + taskfile: ./vhs/Taskfile.yml 6 + dir: ./vhs 7 + 8 + tasks: 9 + lint: 10 + cmds: 11 + - selene . 12 + - stylua --check . 13 + 14 + format: 15 + cmd: stylua . 16 + 17 + test: 18 + cmds: 19 + - | 20 + nvim --clean --headless \ 21 + -u ./scripts/minimal_init.lua \ 22 + -c "lua MiniTest.run()" \ 23 + -c ":qa!" 24 + nvim: 25 + cmd: nvim --clean -u "./scripts/minimal_init.lua" {{ .CLI_ARGS }} 26 + 27 + docgen: 28 + desc: generate vimhelp 29 + cmds: 30 + - | 31 + nvim --clean --headless \ 32 + -u "./scripts/minimal_init.lua" \ 33 + -c "luafile ./scripts/docgen.lua" \ 34 + -c ":qa!" 35 + 36 + install-deps: 37 + desc: installs go bin (used in CI) 38 + cmds: 39 + - | 40 + nvim --clean --headless \ 41 + -u "./scripts/minimal_init.lua" \ 42 + +GoInstallDepsSync +qa
-3
autoload/health/gopher.vim
··· 1 - function! health#gopher#check() 2 - lua require"gopher.health".check() 3 - endfunction
+1
doc/.gitignore
··· 1 + /tags
+294
doc/gopher.nvim.txt
··· 1 + *gopher.nvim* Enhance your golang experience 2 + 3 + MIT License Copyright (c) 2025 Oleksandr Smirnov 4 + 5 + ============================================================================== 6 + 7 + gopher.nvim is a minimalistic plugin for Go development in Neovim written in Lua. 8 + It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim. 9 + 10 + Table of Contents 11 + Setup ................................................ |gopher.nvim-setup()| 12 + Install dependencies ............................ |gopher.nvim-dependencies| 13 + Config ................................................ |gopher.nvim-config| 14 + Commands ............................................ |gopher.nvim-commands| 15 + Modify struct tags ............................... |gopher.nvim-struct-tags| 16 + json2go .............................................. |gopher.nvim-json2go| 17 + Auto implementation of interface methods ................ |gopher.nvim-impl| 18 + Generating unit tests boilerplate .................... |gopher.nvim-gotests| 19 + Iferr .................................................. |gopher.nvim-iferr| 20 + Generate comments ................................... |gopher.nvim-comments| 21 + 22 + ------------------------------------------------------------------------------ 23 + *gopher.nvim-setup()* 24 + `gopher.setup`({user_config}) 25 + Setup function. This method simply merges default config with opts table. 26 + You can read more about configuration at |gopher.nvim-config| 27 + Calling this function is optional, if you ok with default settings. 28 + See |gopher.nvim.config| 29 + 30 + Usage ~ 31 + >lua 32 + require("gopher").setup {} -- use default config or replace {} with your own 33 + < 34 + Parameters ~ 35 + {user_config} `(gopher.Config)` See |gopher.nvim-config| 36 + 37 + ------------------------------------------------------------------------------ 38 + *gopher.nvim-dependencies* 39 + `gopher.install_deps` 40 + 41 + Gopher.nvim implements most of its features using third-party tools. To 42 + install plugin's dependencies, you can run: 43 + `:GoInstallDeps` or `:GoInstallDepsSync` 44 + or use `require("gopher").install_deps()` if you prefer lua api. 45 + 46 + 47 + ============================================================================== 48 + ------------------------------------------------------------------------------ 49 + *gopher.nvim-config* 50 + `default_config` 51 + >lua 52 + local default_config = { 53 + -- log level, you might consider using DEBUG or TRACE for debugging the plugin 54 + ---@type number 55 + log_level = vim.log.levels.INFO, 56 + 57 + -- timeout for running internal commands 58 + ---@type number 59 + timeout = 2000, 60 + 61 + -- timeout for running installer commands(e.g :GoDepsInstall, :GoDepsInstallSync) 62 + ---@type number 63 + installer_timeout = 999999, 64 + 65 + -- user specified paths to binaries 66 + ---@class gopher.ConfigCommand 67 + commands = { 68 + go = "go", 69 + gomodifytags = "gomodifytags", 70 + gotests = "gotests", 71 + impl = "impl", 72 + iferr = "iferr", 73 + json2go = "json2go", 74 + }, 75 + ---@class gopher.ConfigGotests 76 + gotests = { 77 + -- a default template that gotess will use. 78 + -- gotets doesn't have template named `default`, we use it to represent absence of the provided template. 79 + template = "default", 80 + 81 + -- path to a directory containing custom test code templates 82 + ---@type string|nil 83 + template_dir = nil, 84 + 85 + -- use named tests(map with test name as key) in table tests(slice of structs by default) 86 + named = false, 87 + }, 88 + ---@class gopher.ConfigGoTag 89 + gotag = { 90 + ---@type gopher.ConfigGoTagTransform 91 + transform = "snakecase", 92 + 93 + -- default tags to add to struct fields 94 + default_tag = "json", 95 + 96 + -- default tag option added struct fields, set to nil to disable 97 + -- e.g: `option = "json=omitempty,xml=omitempty` 98 + ---@type string|nil 99 + option = nil, 100 + }, 101 + ---@class gopher.ConfigIfErr 102 + iferr = { 103 + -- choose a custom error message, nil to use default 104 + -- e.g: `message = 'fmt.Errorf("failed to %w", err)'` 105 + ---@type string|nil 106 + message = nil, 107 + }, 108 + ---@class gopher.ConfigJson2Go 109 + json2go = { 110 + -- command used to open interactive input. 111 + -- e.g: `split`, `botright split`, `tabnew` 112 + interactive_cmd = "vsplit", 113 + 114 + -- name of autogenerated struct, if nil none, will the default one of json2go. 115 + -- e.g: "MySuperCoolName" 116 + ---@type string|nil 117 + type_name = nil, 118 + }, 119 + } 120 + < 121 + Class ~ 122 + {gopher.Config} 123 + Fields ~ 124 + {setup} `(fun(user_config?: gopher.Config))` 125 + 126 + 127 + ============================================================================== 128 + ------------------------------------------------------------------------------ 129 + *gopher.nvim-commands* 130 + 131 + If don't want to automatically register plugins' commands, 132 + you can set `vim.g.gopher_register_commands` to `false`, before loading the plugin. 133 + 134 + 135 + ============================================================================== 136 + ------------------------------------------------------------------------------ 137 + *gopher.nvim-struct-tags* 138 + 139 + `struct_tags` is utilizing the `gomodifytags` tool to add or remove tags to 140 + struct fields. 141 + 142 + Usage ~ 143 + 144 + How to add/remove/clear tags to struct fields: 145 + 1. Place cursor on the struct 146 + 2. Run `:GoTagAdd json` to add json tags to struct fields 147 + 3. Run `:GoTagRm json` to remove json tags to struct fields 148 + 4. Run `:GoTagClear` to clear all tags from struct fields 149 + 150 + If you want to add/remove tag with options, you can use `json=omitempty` 151 + (where json is tag, and omitempty is its option). 152 + Example: `:GoTagAdd xml json=omitempty` 153 + 154 + 155 + NOTE: if you dont specify the tag it will use `json` as default 156 + 157 + Example: 158 + >go 159 + // before 160 + type User struct { 161 + // ^ put your cursor here 162 + // run `:GoTagAdd yaml` 163 + ID int 164 + Name string 165 + } 166 + 167 + // after 168 + type User struct { 169 + ID int `yaml:id` 170 + Name string `yaml:name` 171 + } 172 + < 173 + 174 + ============================================================================== 175 + ------------------------------------------------------------------------------ 176 + *gopher.nvim-json2go* 177 + 178 + Convert json to go type annotations. 179 + 180 + Usage ~ 181 + 182 + `:GoJson` opens a temporary buffer where you can paste or write JSON. 183 + Saving the buffer (`:w` or `:wq`) automatically closes it and inserts the 184 + generated Go struct into the original buffer at the cursor position. 185 + 186 + Alternatively, you can pass JSON directly as an argument: 187 + >vim 188 + :GoJson {"name": "Alice", "age": 30} 189 + < 190 + ------------------------------------------------------------------------------ 191 + *json2go.transform()* 192 + `json2go.transform`({json_str}) 193 + 194 + Parameters ~ 195 + {json_str} `(string)` Json string that is going to be converted to go type. 196 + Return ~ 197 + `(string)` `(optional)` Go type, or nil if failed. 198 + 199 + ------------------------------------------------------------------------------ 200 + *json2go.json2go()* 201 + `json2go.json2go`({json_str}) 202 + Converts json string to go type, and puts result under the cursor. If 203 + [json_str] is nil, will open an interactive prompt (with cmd set in 204 + config). 205 + 206 + Parameters ~ 207 + {json_str} `(optional)` `(string)` 208 + 209 + 210 + ============================================================================== 211 + ------------------------------------------------------------------------------ 212 + *gopher.nvim-impl* 213 + 214 + Integration of `impl` tool to generate method stubs for interfaces. 215 + 216 + Usage ~ 217 + 218 + 1. Automatically implement an interface for a struct: 219 + - Place your cursor on the struct where you want to implement the interface. 220 + - Run `:GoImpl io.Reader` 221 + - This will automatically determine the receiver and implement the `io.Reader` interface. 222 + 223 + 2. Specify a custom receiver: 224 + - Place your cursor on the struct 225 + - Run `:GoImpl w io.Writer`, where: 226 + - `w` is the receiver. 227 + - `io.Writer` is the interface to implement. 228 + 229 + 3. Explicitly specify the receiver, struct, and interface: 230 + - No need to place the cursor on the struct if all arguments are provided. 231 + - Run `:GoImpl r RequestReader io.Reader`, where: 232 + - `r` is the receiver. 233 + - `RequestReader` is the struct. 234 + - `io.Reader` is the interface to implement. 235 + 236 + Example: 237 + >go 238 + type BytesReader struct{} 239 + // ^ put your cursor here 240 + // run `:GoImpl b io.Reader` 241 + 242 + // this is what you will get 243 + func (b *BytesReader) Read(p []byte) (n int, err error) { 244 + panic("not implemented") // TODO: Implement 245 + } 246 + < 247 + 248 + ============================================================================== 249 + ------------------------------------------------------------------------------ 250 + *gopher.nvim-gotests* 251 + gotests is utilizing the `gotests` tool to generate unit tests boilerplate. 252 + Usage ~ 253 + 254 + - Generate unit test for specific function/method: 255 + 1. Place your cursor on the desired function/method. 256 + 2. Run `:GoTestAdd` 257 + 258 + - Generate unit tests for *all* functions/methods in current file: 259 + - run `:GoTestsAll` 260 + 261 + - Generate unit tests *only* for *exported(public)* functions/methods: 262 + - run `:GoTestsExp` 263 + 264 + You can also specify the template to use for generating the tests. 265 + See |gopher.nvim-config|. 266 + More details about templates: https://github.com/cweill/gotests 267 + 268 + If you prefer named tests, you can enable them in |gopher.nvim-config|. 269 + 270 + 271 + ============================================================================== 272 + ------------------------------------------------------------------------------ 273 + *gopher.nvim-iferr* 274 + 275 + `iferr` provides a way to way to automatically insert `if err != nil` check. 276 + If you want to change `-message` option of `iferr` tool, see |gopher.nvim-config| 277 + 278 + Usage ~ 279 + Execute `:GoIfErr` near any `err` variable to insert the check 280 + 281 + 282 + ============================================================================== 283 + ------------------------------------------------------------------------------ 284 + *gopher.nvim-comments* 285 + 286 + This module provides a way to generate comments for Go code. 287 + 288 + Usage ~ 289 + 290 + Set cursor on line with function/method/struct/etc and 291 + run `:GoCmt` to generate a comment. 292 + 293 + 294 + vim:tw=78:ts=8:noet:ft=help:norl:
+63 -47
lua/gopher/_utils/init.lua
··· 1 - return { 2 - ---@param t table 3 - ---@return boolean 4 - empty = function(t) 5 - if t == nil then 6 - return true 7 - end 1 + local c = require "gopher.config" 2 + local log = require "gopher._utils.log" 3 + local utils = {} 8 4 9 - return next(t) == nil 10 - end, 5 + ---@param msg string 6 + ---@param lvl? integer by default `vim.log.levels.INFO` 7 + function utils.notify(msg, lvl) 8 + lvl = lvl or vim.log.levels.INFO 9 + vim.notify(msg, lvl, { 10 + ---@diagnostic disable-next-line:undefined-field 11 + title = c.___plugin_name, 12 + }) 13 + log.debug(msg) 14 + end 15 + 16 + ---@param path string 17 + ---@return string 18 + function utils.readfile_joined(path) 19 + return table.concat(vim.fn.readfile(path), "\n") 20 + end 11 21 12 - ---@param s string 13 - ---@return string 14 - rtrim = function(s) 15 - local n = #s 16 - while n > 0 and s:find("^%s", n) do 17 - n = n - 1 22 + ---@param t string[] 23 + ---@return string[] 24 + function utils.remove_empty_lines(t) 25 + local res = {} 26 + for _, line in ipairs(t) do 27 + if line ~= "" then 28 + table.insert(res, line) 18 29 end 30 + end 31 + return res 32 + end 19 33 20 - return s:sub(1, n) 21 - end, 22 - 23 - ---@param lib string 24 - ---@return boolean 25 - lualib_is_found = function(lib) 26 - local is_found, _ = pcall(require, lib) 27 - return is_found 28 - end, 34 + ---@param s string 35 + ---@return string 36 + function utils.trimend(s) 37 + local r, _ = string.gsub(s, "%s+$", "") 38 + return r 39 + end 29 40 30 - ---@param bin string 31 - ---@return boolean 32 - binary_is_found = function(bin) 33 - if vim.fn.executable(bin) == 1 then 34 - return true 35 - end 41 + -- Since indentation can be spaces or tabs, that's my hack around it 42 + ---@param line string 43 + ---@param indent integer 44 + ---@return string 45 + function utils.indent(line, indent) 46 + local char = string.sub(line, 1, 1) 47 + if char ~= " " and char ~= "\t" then 48 + char = " " 49 + end 50 + return string.rep(char, indent) 51 + end 36 52 37 - return false 38 - end, 53 + ---@generic T 54 + ---@param tbl T[] 55 + ---@return T[] 56 + function utils.list_unique(tbl) 57 + if vim.fn.has "nvim-0.12" == 1 then 58 + return vim.list.unique(tbl) 59 + end 39 60 40 - ---@param msg string 41 - ---@param lvl string|integer 42 - notify = function(msg, lvl) 43 - local l 44 - if lvl == "error" or lvl == 4 then 45 - l = vim.log.levels.ERROR 46 - elseif lvl == "info" or lvl == 2 then 47 - l = vim.log.levels.INFO 48 - elseif lvl == "debug" or lvl == 1 then 49 - l = vim.log.levels.DEBUG 61 + for i = #tbl, 1, -1 do 62 + for j = 1, i - 1 do 63 + if tbl[i] == tbl[j] then 64 + table.remove(tbl, i) 65 + break 66 + end 50 67 end 68 + end 69 + return tbl 70 + end 51 71 52 - vim.defer_fn(function() 53 - vim.notify(msg, l) 54 - end, 0) 55 - end, 56 - } 72 + return utils
+171
lua/gopher/_utils/log.lua
··· 1 + -- thanks https://github.com/tjdevries/vlog.nvim 2 + -- and https://github.com/williamboman/mason.nvim 3 + -- for the code i have stolen(or have inspected by idk) 4 + local c = require "gopher.config" 5 + 6 + ---@class gopher.Logger 7 + ---@field get_outfile fun():string 8 + ---@field trace fun(...) 9 + ---@field fmt_trace fun(...) 10 + ---@field debug fun(...) 11 + ---@field fmt_debug fun(...) 12 + ---@field info fun(...) 13 + ---@field fmt_info fun(...) 14 + ---@field warn fun(...) 15 + ---@field fmt_warn fun(...) 16 + ---@field error fun(...) 17 + ---@field fmt_error fun(...) 18 + 19 + local config = { 20 + -- Name of the plugin. Prepended to log messages 21 + ---@diagnostic disable-next-line:undefined-field 22 + name = c.___plugin_name, 23 + 24 + -- Should print the output to neovim while running 25 + -- values: 'sync','async',false 26 + use_console = vim.env.GOPHER_VERBOSE_LOGS == "1", 27 + 28 + -- Should highlighting be used in console (using echohl) 29 + highlights = true, 30 + 31 + -- Should write to a file 32 + use_file = true, 33 + 34 + -- Level configuration 35 + modes = { 36 + { name = "trace", hl = "Comment", level = vim.log.levels.TRACE }, 37 + { name = "debug", hl = "Comment", level = vim.log.levels.DEBUG }, 38 + { name = "info", hl = "None", level = vim.log.levels.INFO }, 39 + { name = "warn", hl = "WarningMsg", level = vim.log.levels.WARN }, 40 + { name = "error", hl = "ErrorMsg", level = vim.log.levels.ERROR }, 41 + }, 42 + 43 + -- Can limit the number of decimals displayed for floats 44 + float_precision = 0.01, 45 + } 46 + 47 + ---@type gopher.Logger 48 + ---@diagnostic disable-next-line: missing-fields 49 + local log = {} 50 + 51 + ---@return string 52 + function log.get_outfile() 53 + return table.concat { 54 + (vim.fn.has "nvim-0.8.0" == 1) and vim.fn.stdpath "log" or vim.fn.stdpath "cache", 55 + ("/%s.log"):format(config.name), 56 + } 57 + end 58 + 59 + -- selene: allow(incorrect_standard_library_use) 60 + local unpack = unpack or table.unpack 61 + 62 + do 63 + local round = function(x, increment) 64 + increment = increment or 1 65 + x = x / increment 66 + return (x > 0 and math.floor(x + 0.5) or math.ceil(x - 0.5)) * increment 67 + end 68 + 69 + local tbl_has_tostring = function(tbl) 70 + local mt = getmetatable(tbl) 71 + return mt and mt.__tostring ~= nil 72 + end 73 + 74 + local make_string = function(...) 75 + local t = {} 76 + for i = 1, select("#", ...) do 77 + local x = select(i, ...) 78 + 79 + if type(x) == "number" and config.float_precision then 80 + x = tostring(round(x, config.float_precision)) 81 + elseif type(x) == "table" and not tbl_has_tostring(x) then 82 + x = vim.inspect(x) 83 + else 84 + x = tostring(x) 85 + end 86 + 87 + t[#t + 1] = x 88 + end 89 + return table.concat(t, " ") 90 + end 91 + 92 + local log_at_level = function(level_config, message_maker, ...) 93 + -- Return early if we're below the current_log_level 94 + -- 95 + -- the log level source get from config directly because otherwise it doesn't work 96 + if level_config.level < c.log_level then 97 + return 98 + end 99 + local nameupper = level_config.name:upper() 100 + 101 + local msg = message_maker(...) 102 + local info = debug.getinfo(2, "Sl") 103 + local lineinfo = info.short_src .. ":" .. info.currentline 104 + 105 + -- Output to console 106 + if config.use_console then 107 + local log_to_console = function() 108 + local console_string = 109 + string.format("[%-6s%s] %s: %s", nameupper, os.date "%H:%M:%S", lineinfo, msg) 110 + 111 + if config.highlights and level_config.hl then 112 + vim.cmd(string.format("echohl %s", level_config.hl)) 113 + end 114 + 115 + local split_console = vim.split(console_string, "\n") 116 + for _, v in ipairs(split_console) do 117 + local formatted_msg = string.format("[%s] %s", config.name, vim.fn.escape(v, [["\]])) 118 + 119 + ---@diagnostic disable-next-line: param-type-mismatch 120 + local ok = pcall(vim.cmd, string.format([[echom "%s"]], formatted_msg)) 121 + if not ok then 122 + vim.api.nvim_out_write(msg .. "\n") 123 + end 124 + end 125 + 126 + if config.highlights and level_config.hl then 127 + vim.cmd "echohl NONE" 128 + end 129 + end 130 + if config.use_console == "sync" and not vim.in_fast_event() then 131 + log_to_console() 132 + else 133 + vim.schedule(log_to_console) 134 + end 135 + end 136 + 137 + -- Output to log file 138 + if config.use_file then 139 + local fp = assert(io.open(log.get_outfile(), "a")) 140 + local str = string.format("[%-6s%s] %s: %s\n", nameupper, os.date(), lineinfo, msg) 141 + fp:write(str) 142 + fp:close() 143 + end 144 + end 145 + 146 + for _, x in ipairs(config.modes) do 147 + -- log.info("these", "are", "separated") 148 + log[x.name] = function(...) ---@diagnostic disable-line: assign-type-mismatch 149 + return log_at_level(x, make_string, ...) 150 + end 151 + 152 + -- log.fmt_info("These are %s strings", "formatted") 153 + log[("fmt_%s"):format(x.name)] = function(...) ---@diagnostic disable-line: assign-type-mismatch 154 + return log_at_level(x, function(...) 155 + local passed = { ... } 156 + local fmt = table.remove(passed, 1) 157 + local inspected = {} 158 + for _, v in ipairs(passed) do 159 + if type(v) == "table" and tbl_has_tostring(v) then 160 + table.insert(inspected, v) 161 + else 162 + table.insert(inspected, vim.inspect(v)) 163 + end 164 + end 165 + return string.format(fmt, unpack(inspected)) 166 + end, ...) 167 + end 168 + end 169 + end 170 + 171 + return log
+39
lua/gopher/_utils/runner.lua
··· 1 + local c = require "gopher.config" 2 + local runner = {} 3 + 4 + ---@class gopher.RunnerOpts 5 + ---@field cwd? string 6 + ---@field timeout? number 7 + ---@field stdin? boolean|string|string[] 8 + ---@field text? boolean 9 + 10 + ---@param cmd (string|number)[] 11 + ---@param on_exit fun(out:vim.SystemCompleted) 12 + ---@param opts? gopher.RunnerOpts 13 + ---@return vim.SystemObj 14 + function runner.async(cmd, on_exit, opts) 15 + opts = opts or {} 16 + return vim.system(cmd, { 17 + cwd = opts.cwd or nil, 18 + timeout = opts.timeout or c.timeout, 19 + stdin = opts.stdin or nil, 20 + text = opts.text or true, 21 + }, on_exit) 22 + end 23 + 24 + ---@param cmd (string|number)[] 25 + ---@param opts? gopher.RunnerOpts 26 + ---@return vim.SystemCompleted 27 + function runner.sync(cmd, opts) 28 + opts = opts or {} 29 + return vim 30 + .system(cmd, { 31 + cwd = opts.cwd or nil, 32 + timeout = opts.timeout or c.timeout, 33 + stdin = opts.stdin or nil, 34 + text = opts.text or true, 35 + }) 36 + :wait() 37 + end 38 + 39 + return runner
-87
lua/gopher/_utils/ts/init.lua
··· 1 - local nodes = require "gopher._utils.ts.nodes" 2 - local u = require "gopher._utils" 3 - local M = { 4 - querys = { 5 - struct_block = [[((type_declaration (type_spec name:(type_identifier) @struct.name type: (struct_type)))@struct.declaration)]], 6 - em_struct_block = [[(field_declaration name:(field_identifier)@struct.name type: (struct_type)) @struct.declaration]], 7 - package = [[(package_clause (package_identifier)@package.name)@package.clause]], 8 - interface = [[((type_declaration (type_spec name:(type_identifier) @interface.name type:(interface_type)))@interface.declaration)]], 9 - method_name = [[((method_declaration receiver: (parameter_list)@method.receiver name: (field_identifier)@method.name body:(block))@method.declaration)]], 10 - func = [[((function_declaration name: (identifier)@function.name) @function.declaration)]], 11 - }, 12 - } 13 - 14 - ---@return table 15 - local function get_name_defaults() 16 - return { 17 - ["func"] = "function", 18 - ["if"] = "if", 19 - ["else"] = "else", 20 - ["for"] = "for", 21 - } 22 - end 23 - 24 - ---@param row string 25 - ---@param col string 26 - ---@param bufnr string|nil 27 - ---@return table|nil 28 - function M.get_struct_node_at_pos(row, col, bufnr) 29 - local query = M.querys.struct_block .. " " .. M.querys.em_struct_block 30 - local bufn = bufnr or vim.api.nvim_get_current_buf() 31 - local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col) 32 - if ns == nil then 33 - u.notify("struct not found", "warn") 34 - else 35 - return ns[#ns] 36 - end 37 - end 38 - 39 - ---@param row string 40 - ---@param col string 41 - ---@param bufnr string|nil 42 - ---@return table|nil 43 - function M.get_func_method_node_at_pos(row, col, bufnr) 44 - local query = M.querys.func .. " " .. M.querys.method_name 45 - local bufn = bufnr or vim.api.nvim_get_current_buf() 46 - local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col) 47 - if ns == nil then 48 - u.notify("function not found", "warn") 49 - else 50 - return ns[#ns] 51 - end 52 - end 53 - 54 - ---@param row string 55 - ---@param col string 56 - ---@param bufnr string|nil 57 - ---@return table|nil 58 - function M.get_package_node_at_pos(row, col, bufnr) 59 - -- stylua: ignore 60 - if row > 10 then return end 61 - local query = M.querys.package 62 - local bufn = bufnr or vim.api.nvim_get_current_buf() 63 - local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col) 64 - if ns == nil then 65 - u.notify("package not found", "warn") 66 - return nil 67 - else 68 - return ns[#ns] 69 - end 70 - end 71 - 72 - ---@param row string 73 - ---@param col string 74 - ---@param bufnr string|nil 75 - ---@return table|nil 76 - function M.get_interface_node_at_pos(row, col, bufnr) 77 - local query = M.querys.interface 78 - local bufn = bufnr or vim.api.nvim_get_current_buf() 79 - local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col) 80 - if ns == nil then 81 - u.notify("interface not found", "warn") 82 - else 83 - return ns[#ns] 84 - end 85 - end 86 - 87 - return M
-137
lua/gopher/_utils/ts/nodes.lua
··· 1 - local ts_query = require "nvim-treesitter.query" 2 - local parsers = require "nvim-treesitter.parsers" 3 - local locals = require "nvim-treesitter.locals" 4 - local u = require "gopher._utils" 5 - local M = {} 6 - 7 - local function intersects(row, col, sRow, sCol, eRow, eCol) 8 - if sRow > row or eRow < row then 9 - return false 10 - end 11 - 12 - if sRow == row and sCol > col then 13 - return false 14 - end 15 - 16 - if eRow == row and eCol < col then 17 - return false 18 - end 19 - 20 - return true 21 - end 22 - 23 - ---@param nodes table 24 - ---@param row string 25 - ---@param col string 26 - ---@return table 27 - function M.intersect_nodes(nodes, row, col) 28 - local found = {} 29 - for idx = 1, #nodes do 30 - local node = nodes[idx] 31 - local sRow = node.dim.s.r 32 - local sCol = node.dim.s.c 33 - local eRow = node.dim.e.r 34 - local eCol = node.dim.e.c 35 - 36 - if intersects(row, col, sRow, sCol, eRow, eCol) then 37 - table.insert(found, node) 38 - end 39 - end 40 - 41 - return found 42 - end 43 - 44 - ---@param nodes table 45 - ---@return table 46 - function M.sort_nodes(nodes) 47 - table.sort(nodes, function(a, b) 48 - return M.count_parents(a) < M.count_parents(b) 49 - end) 50 - 51 - return nodes 52 - end 53 - 54 - ---@param query string 55 - ---@param lang string 56 - ---@param bufnr integer 57 - ---@param pos_row string 58 - ---@return string 59 - function M.get_all_nodes(query, lang, _, bufnr, pos_row, _) 60 - bufnr = bufnr or 0 61 - pos_row = pos_row or 30000 62 - 63 - local ok, parsed_query = pcall(function() 64 - return vim.treesitter.parse_query(lang, query) 65 - end) 66 - if not ok then 67 - return nil 68 - end 69 - 70 - local parser = parsers.get_parser(bufnr, lang) 71 - local root = parser:parse()[1]:root() 72 - local start_row, _, end_row, _ = root:range() 73 - local results = {} 74 - 75 - for match in ts_query.iter_prepared_matches(parsed_query, root, bufnr, start_row, end_row) do 76 - local sRow, sCol, eRow, eCol, declaration_node 77 - local type, name, op = "", "", "" 78 - locals.recurse_local_nodes(match, function(_, node, path) 79 - local idx = string.find(path, ".[^.]*$") 80 - op = string.sub(path, idx + 1, #path) 81 - type = string.sub(path, 1, idx - 1) 82 - 83 - if op == "name" then 84 - name = vim.treesitter.query.get_node_text(node, bufnr) 85 - elseif op == "declaration" or op == "clause" then 86 - declaration_node = node 87 - sRow, sCol, eRow, eCol = node:range() 88 - sRow = sRow + 1 89 - eRow = eRow + 1 90 - sCol = sCol + 1 91 - eCol = eCol + 1 92 - end 93 - end) 94 - 95 - if declaration_node ~= nil then 96 - table.insert(results, { 97 - declaring_node = declaration_node, 98 - dim = { s = { r = sRow, c = sCol }, e = { r = eRow, c = eCol } }, 99 - name = name, 100 - operator = op, 101 - type = type, 102 - }) 103 - end 104 - end 105 - 106 - return results 107 - end 108 - 109 - ---@param query string 110 - ---@param default string 111 - ---@param bufnr string 112 - ---@param row string 113 - ---@param col string 114 - ---@return table 115 - function M.nodes_at_cursor(query, default, bufnr, row, col) 116 - bufnr = bufnr or vim.api.nvim_get_current_buf() 117 - local ft = vim.api.nvim_buf_get_option(bufnr, "ft") 118 - if row == nil or col == nil then 119 - row, col = unpack(vim.api.nvim_win_get_cursor(0)) 120 - end 121 - 122 - local nodes = M.get_all_nodes(query, ft, default, bufnr, row, col) 123 - if nodes == nil then 124 - u.notify("Unable to find any nodes. Place your cursor on a go symbol and try again", "debug") 125 - return nil 126 - end 127 - 128 - nodes = M.sort_nodes(M.intersect_nodes(nodes, row, col)) 129 - if nodes == nil or #nodes == 0 then 130 - u.notify("Unable to find any nodes at pos. " .. tostring(row) .. ":" .. tostring(col), "debug") 131 - return nil 132 - end 133 - 134 - return nodes 135 - end 136 - 137 - return M
+160
lua/gopher/_utils/ts.lua
··· 1 + local ts = {} 2 + local queries = { 3 + struct = [[ 4 + [(type_spec name: (type_identifier) @_name 5 + type: (struct_type)) 6 + (var_declaration (var_spec 7 + name: (identifier) @_name @_var 8 + type: (struct_type))) 9 + (short_var_declaration 10 + left: (expression_list (identifier) @_name @_var) 11 + right: (expression_list (composite_literal 12 + type: (struct_type))))] 13 + ]], 14 + struct_field = [[ 15 + (field_declaration name: (field_identifier) @_name) 16 + ]], 17 + func = [[ 18 + [(function_declaration name: (identifier) @_name) 19 + (method_declaration name: (field_identifier) @_name) 20 + (method_elem name: (field_identifier) @_name)] 21 + ]], 22 + package = [[ 23 + (package_identifier) @_name 24 + ]], 25 + interface = [[ 26 + (type_spec 27 + name: (type_identifier) @_name 28 + type: (interface_type)) 29 + ]], 30 + var = [[ 31 + [(var_declaration (var_spec name: (identifier) @_name)) 32 + (short_var_declaration 33 + left: (expression_list (identifier) @_name @_var))] 34 + ]], 35 + } 36 + 37 + ---@param parent_type string[] 38 + ---@param node TSNode 39 + ---@return TSNode? 40 + local function get_parent_node(parent_type, node) 41 + ---@type TSNode? 42 + local current = node 43 + while current do 44 + if vim.tbl_contains(parent_type, current:type()) then 45 + break 46 + end 47 + 48 + current = current:parent() 49 + if current == nil then 50 + return nil 51 + end 52 + end 53 + return current 54 + end 55 + 56 + ---@param query vim.treesitter.Query 57 + ---@param node TSNode 58 + ---@param bufnr integer 59 + ---@return {name:string, is_varstruct:boolean} 60 + local function get_captures(query, node, bufnr) 61 + local res = {} 62 + for id, _node in query:iter_captures(node, bufnr) do 63 + if query.captures[id] == "_name" then 64 + res["name"] = vim.treesitter.get_node_text(_node, bufnr) 65 + end 66 + 67 + if query.captures[id] == "_var" then 68 + res["is_varstruct"] = true 69 + end 70 + end 71 + 72 + return res 73 + end 74 + 75 + ---@class gopher.TsResult 76 + ---@field name string Name of the struct, function, etc 77 + ---@field start integer Line number where the declaration starts 78 + ---@field end_ integer Line number where the declaration ends 79 + ---@field indent integer Number of spaces/tabs in the current cursor line 80 + ---@field is_varstruct boolean Is struct declared as `var S struct{}` or `s := struct{}{}` 81 + 82 + ---@param bufnr integer 83 + ---@param parent_type string[] 84 + ---@param query string 85 + ---@return gopher.TsResult 86 + local function do_stuff(bufnr, parent_type, query) 87 + if not vim.treesitter.get_parser(bufnr, "go") then 88 + error "No treesitter parser found for go" 89 + end 90 + 91 + local node = vim.treesitter.get_node { bufnr = bufnr } 92 + if not node then 93 + error "No nodes found under the cursor" 94 + end 95 + 96 + local parent_node = get_parent_node(parent_type, node) 97 + if not parent_node then 98 + error "No parent node found under the cursor" 99 + end 100 + 101 + local q = vim.treesitter.query.parse("go", query) 102 + local res = get_captures(q, parent_node, bufnr) 103 + assert(res.name ~= nil, "No capture name found") 104 + 105 + local start_row, start_col, end_row, _ = parent_node:range() 106 + res["indent"] = start_col 107 + res["start"] = start_row + 1 108 + res["end_"] = end_row + 1 109 + 110 + return res 111 + end 112 + 113 + ---@param bufnr integer 114 + function ts.get_struct_under_cursor(bufnr) 115 + --- should be both type_spec and type_declaration 116 + --- because in cases like `type ( T struct{}, U struct{} )` 117 + --- 118 + --- var_declaration is for cases like `var x struct{}` 119 + --- short_var_declaration is for cases like `x := struct{}{}` 120 + --- 121 + --- it always chooses last struct type in the list 122 + return do_stuff(bufnr, { 123 + "type_spec", 124 + "type_declaration", 125 + "var_declaration", 126 + "short_var_declaration", 127 + }, queries.struct) 128 + end 129 + 130 + ---@param bufnr integer 131 + function ts.get_struct_field_under_cursor(bufnr) 132 + return do_stuff(bufnr, { "field_declaration" }, queries.struct_field) 133 + end 134 + 135 + ---@param bufnr integer 136 + function ts.get_func_under_cursor(bufnr) 137 + --- since this handles both and funcs and methods we should check for both parent nodes 138 + return do_stuff(bufnr, { 139 + "method_elem", 140 + "function_declaration", 141 + "method_declaration", 142 + }, queries.func) 143 + end 144 + 145 + ---@param bufnr integer 146 + function ts.get_package_under_cursor(bufnr) 147 + return do_stuff(bufnr, { "package_clause" }, queries.package) 148 + end 149 + 150 + ---@param bufnr integer 151 + function ts.get_interface_under_cursor(bufnr) 152 + return do_stuff(bufnr, { "type_declaration" }, queries.interface) 153 + end 154 + 155 + ---@param bufnr integer 156 + function ts.get_variable_under_cursor(bufnr) 157 + return do_stuff(bufnr, { "var_declaration", "short_var_declaration" }, queries.var) 158 + end 159 + 160 + return ts
+55 -34
lua/gopher/comment.lua
··· 1 - local ts_utils = require "gopher._utils.ts" 1 + ---@toc_entry Generate comments 2 + ---@tag gopher.nvim-comments 3 + ---@text 4 + --- This module provides a way to generate comments for Go code. 5 + --- 6 + ---@usage 7 + --- Set cursor on line with function/method/struct/etc and 8 + --- run `:GoCmt` to generate a comment. 2 9 3 - local function generate(row, col) 4 - local comment, ns = nil, nil 10 + local ts = require "gopher._utils.ts" 11 + local log = require "gopher._utils.log" 12 + local u = require "gopher._utils" 13 + local comment = {} 5 14 6 - ns = ts_utils.get_package_node_at_pos(row, col) 7 - if ns ~= nil and ns ~= {} then 8 - comment = "// Package " .. ns.name .. " provides " .. ns.name 9 - return comment, ns 15 + --- NOTE: The order of functions executed inside this function is IMPORTANT. 16 + --- This function is extremely fragile; run tests after making any changes. 17 + --- 18 + ---@param bufnr integer 19 + ---@param line string 20 + ---@return string 21 + ---@dochide 22 + local function generate(bufnr, line) 23 + local sf_ok, sf_res = pcall(ts.get_struct_field_under_cursor, bufnr) 24 + if sf_ok then 25 + return u.indent(line, sf_res.indent) .. "// " .. sf_res.name .. " " 10 26 end 11 27 12 - ns = ts_utils.get_struct_node_at_pos(row, col) 13 - if ns ~= nil and ns ~= {} then 14 - comment = "// " .. ns.name .. " " .. ns.type .. " " 15 - return comment, ns 28 + local s_ok, s_res = pcall(ts.get_struct_under_cursor, bufnr) 29 + if s_ok then 30 + return u.indent(line, s_res.indent) .. "// " .. s_res.name .. " " 16 31 end 17 32 18 - ns = ts_utils.get_func_method_node_at_pos(row, col) 19 - if ns ~= nil and ns ~= {} then 20 - comment = "// " .. ns.name .. " " .. ns.type .. " " 21 - return comment, ns 33 + local v_ok, v_res = pcall(ts.get_variable_under_cursor, bufnr) 34 + if v_ok then 35 + return u.indent(line, v_res.indent) .. "// " .. v_res.name .. " " 22 36 end 23 37 24 - ns = ts_utils.get_interface_node_at_pos(row, col) 25 - if ns ~= nil and ns ~= {} then 26 - comment = "// " .. ns.name .. " " .. ns.type .. " " 27 - return comment, ns 38 + local f_ok, f_res = pcall(ts.get_func_under_cursor, bufnr) 39 + if f_ok then 40 + return u.indent(line, f_res.indent) .. "// " .. f_res.name .. " " 28 41 end 29 42 30 - return "// ", {} 31 - end 32 - 33 - return function() 34 - local row, col = unpack(vim.api.nvim_win_get_cursor(0)) 35 - local comment, ns = generate(row + 1, col + 1) 43 + local i_ok, i_res = pcall(ts.get_interface_under_cursor, bufnr) 44 + if i_ok then 45 + return u.indent(line, i_res.indent) .. "// " .. i_res.name .. " " 46 + end 36 47 37 - vim.api.nvim_win_set_cursor(0, { 38 - ns.dim.s.r, 39 - ns.dim.s.c, 40 - }) 48 + local p_ok, p_res = pcall(ts.get_package_under_cursor, bufnr) 49 + if p_ok then 50 + return "// Package " .. p_res.name .. " provides " 51 + end 41 52 42 - vim.fn.append(row - 1, comment) 53 + return "// " 54 + end 43 55 44 - vim.api.nvim_win_set_cursor(0, { 45 - ns.dim.s.r, 46 - #comment + 1, 56 + function comment.comment() 57 + local bufnr = vim.api.nvim_get_current_buf() 58 + local lnum = vim.fn.getcurpos()[2] 59 + local line = vim.fn.getline(lnum) 60 + local cmt = generate(bufnr, line) 61 + log.debug("generated comment:", { 62 + comment = cmt, 63 + line = line, 47 64 }) 48 65 49 - vim.cmd [[startinsert!]] 66 + vim.fn.append(lnum - 1, cmt) 67 + vim.fn.setpos(".", { bufnr, lnum, #cmt }) 68 + vim.cmd "startinsert!" 50 69 end 70 + 71 + return comment
+137 -14
lua/gopher/config.lua
··· 1 - local M = { 2 - config = { 3 - ---set custom commands for tools 4 - commands = { 5 - go = "go", 6 - gomodifytags = "gomodifytags", 7 - gotests = "gotests", 8 - impl = "impl", 9 - }, 1 + ---@type gopher.Config 2 + ---@dochide 3 + ---@diagnostic disable-next-line: missing-fields .setup() gets injected later 4 + local config = {} 5 + 6 + ---@tag gopher.nvim-config.ConfigGoTagTransform 7 + ---@text Possible values for |gopher.Config|.gotag.transform: 8 + --- 9 + ---@dochide 10 + ---@alias gopher.ConfigGoTagTransform 11 + ---| "snakecase" "GopherUser" -> "gopher_user" 12 + ---| "camelcase" "GopherUser" -> "gopherUser" 13 + ---| "lispcase" "GopherUser" -> "gopher-user" 14 + ---| "pascalcase" "GopherUser" -> "GopherUser" 15 + ---| "titlecase" "GopherUser" -> "Gopher User" 16 + ---| "keep" keeps the original field name 17 + 18 + ---@toc_entry Config 19 + ---@tag gopher.nvim-config 20 + ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section) 21 + ---@class gopher.Config 22 + ---@field setup fun(user_config?: gopher.Config) 23 + local default_config = { 24 + -- log level, you might consider using DEBUG or TRACE for debugging the plugin 25 + ---@type number 26 + log_level = vim.log.levels.INFO, 27 + 28 + -- timeout for running internal commands 29 + ---@type number 30 + timeout = 2000, 31 + 32 + -- timeout for running installer commands(e.g :GoDepsInstall, :GoDepsInstallSync) 33 + ---@type number 34 + installer_timeout = 999999, 35 + 36 + -- user specified paths to binaries 37 + ---@class gopher.ConfigCommand 38 + commands = { 39 + go = "go", 40 + gomodifytags = "gomodifytags", 41 + gotests = "gotests", 42 + impl = "impl", 43 + iferr = "iferr", 44 + json2go = "json2go", 45 + }, 46 + ---@class gopher.ConfigGotests 47 + gotests = { 48 + -- a default template that gotess will use. 49 + -- gotets doesn't have template named `default`, we use it to represent absence of the provided template. 50 + template = "default", 51 + 52 + -- path to a directory containing custom test code templates 53 + ---@type string|nil 54 + template_dir = nil, 55 + 56 + -- use named tests(map with test name as key) in table tests(slice of structs by default) 57 + named = false, 58 + }, 59 + ---@class gopher.ConfigGoTag 60 + gotag = { 61 + ---@type gopher.ConfigGoTagTransform 62 + transform = "snakecase", 63 + 64 + -- default tags to add to struct fields 65 + default_tag = "json", 66 + 67 + -- default tag option added struct fields, set to nil to disable 68 + -- e.g: `option = "json=omitempty,xml=omitempty` 69 + ---@type string|nil 70 + option = nil, 71 + }, 72 + ---@class gopher.ConfigIfErr 73 + iferr = { 74 + -- choose a custom error message, nil to use default 75 + -- e.g: `message = 'fmt.Errorf("failed to %w", err)'` 76 + ---@type string|nil 77 + message = nil, 78 + }, 79 + ---@class gopher.ConfigJson2Go 80 + json2go = { 81 + -- command used to open interactive input. 82 + -- e.g: `split`, `botright split`, `tabnew` 83 + interactive_cmd = "vsplit", 84 + 85 + -- name of autogenerated struct, if nil none, will the default one of json2go. 86 + -- e.g: "MySuperCoolName" 87 + ---@type string|nil 88 + type_name = nil, 10 89 }, 11 90 } 91 + --minidoc_afterlines_end 92 + 93 + ---@type gopher.Config 94 + ---@dochide 95 + local _config = default_config 96 + 97 + -- I am kinda secret so don't tell anyone about me even dont use me 98 + -- 99 + -- if you don't believe me that i am secret see 100 + -- the line below it says @private 101 + ---@private 102 + _config.___plugin_name = "gopher.nvim" ---@diagnostic disable-line: inject-field 103 + 104 + ---@param user_config? gopher.Config 105 + ---@dochide 106 + function config.setup(user_config) 107 + vim.validate("user_config", user_config, "table", true) 12 108 13 - ---Plugin setup function 14 - ---@param opts table user options 15 - function M.setup(opts) 16 - M.config = vim.tbl_deep_extend("force", M.config, opts) 109 + _config = vim.tbl_deep_extend("force", vim.deepcopy(default_config), user_config or {}) 110 + 111 + vim.validate("log_level", _config.log_level, "number") 112 + vim.validate("timeout", _config.timeout, "number") 113 + vim.validate("installer_timeout", _config.timeout, "number") 114 + vim.validate("commands", _config.commands, "table") 115 + vim.validate("commands.go", _config.commands.go, "string") 116 + vim.validate("commands.gomodifytags", _config.commands.gomodifytags, "string") 117 + vim.validate("commands.gotests", _config.commands.gotests, "string") 118 + vim.validate("commands.impl", _config.commands.impl, "string") 119 + vim.validate("commands.iferr", _config.commands.iferr, "string") 120 + vim.validate("commands.json2go", _config.commands.json2go, "string") 121 + vim.validate("gotests", _config.gotests, "table") 122 + vim.validate("gotests.template", _config.gotests.template, "string") 123 + vim.validate("gotests.template_dir", _config.gotests.template_dir, { "string", "nil" }) 124 + vim.validate("gotests.named", _config.gotests.named, "boolean") 125 + vim.validate("gotag", _config.gotag, "table") 126 + vim.validate("gotag.transform", _config.gotag.transform, "string") 127 + vim.validate("gotag.default_tag", _config.gotag.default_tag, "string") 128 + vim.validate("gotag.option", _config.gotag.option, { "string", "nil" }) 129 + vim.validate("iferr", _config.iferr, "table") 130 + vim.validate("iferr.message", _config.iferr.message, { "string", "nil" }) 131 + vim.validate("json2go.installer_timeout", _config.json2go.interactive_cmd, "string") 132 + vim.validate("json2go.type_name", _config.json2go.type_name, { "string", "nil" }) 17 133 end 18 134 19 - return M 135 + setmetatable(config, { 136 + __index = function(_, key) 137 + return _config[key] 138 + end, 139 + }) 140 + 141 + ---@dochide 142 + return config
+56
lua/gopher/go.lua
··· 1 + local c = require "gopher.config" 2 + local u = require "gopher._utils" 3 + local r = require "gopher._utils.runner" 4 + local go = {} 5 + 6 + local function run(subcmd, args) 7 + local rs = r.sync { c.commands.go, subcmd, unpack(args) } 8 + if rs.code ~= 0 then 9 + error("go " .. subcmd .. " failed: " .. rs.stderr) 10 + end 11 + 12 + u.notify(c.commands.go .. " " .. subcmd .. " ran successful") 13 + return rs.stdout 14 + end 15 + 16 + ---@param args string[] 17 + function go.get(args) 18 + for i, arg in ipairs(args) do 19 + local m = string.match(arg, "^https://(.*)$") or string.match(arg, "^http://(.*)$") or arg 20 + table.remove(args, i) 21 + table.insert(args, i, m) 22 + end 23 + 24 + run("get", args) 25 + end 26 + 27 + ---@param args string[] 28 + function go.mod(args) 29 + run("mod", args) 30 + end 31 + 32 + ---@param args string[] 33 + function go.work(args) 34 + -- TODO: use `gopls.tidy` 35 + 36 + run("work", args) 37 + end 38 + 39 + ---Executes `go generate` 40 + ---If only argument is `%` it's going to be equivalent to `go generate <path to current file>` 41 + ---@param args string[] 42 + function go.generate(args) 43 + -- TODO: use `gopls.generate` 44 + 45 + if #args == 0 then 46 + error "please provide arguments" 47 + end 48 + 49 + if #args == 1 and args[1] == "%" then 50 + args[1] = vim.fn.expand "%" 51 + end 52 + 53 + run("generate", args) 54 + end 55 + 56 + return go
-28
lua/gopher/gogenerate.lua
··· 1 - local Job = require "plenary.job" 2 - local c = require("gopher.config").config.commands 3 - local u = require "gopher._utils" 4 - 5 - ---run "go generate" 6 - return function(...) 7 - local args = { ... } 8 - if #args == 1 and args[1] == "%" then 9 - args[1] = vim.fn.expand "%" ---@diagnostic disable-line: missing-parameter 10 - end 11 - 12 - local cmd_args = vim.list_extend({ "generate" }, args) 13 - 14 - Job 15 - :new({ 16 - command = c.go, 17 - args = cmd_args, 18 - on_exit = function(_, retval) 19 - if retval ~= 0 then 20 - u.notify("command 'go " .. unpack(cmd_args) .. "' exited with code " .. retval, "error") 21 - return 22 - end 23 - 24 - u.notify("go generate was success runned", "info") 25 - end, 26 - }) 27 - :start() 28 - end
-35
lua/gopher/goget.lua
··· 1 - local Job = require "plenary.job" 2 - local c = require("gopher.config").config.commands 3 - local u = require "gopher._utils" 4 - 5 - ---run "go get" 6 - return function(...) 7 - local args = { ... } 8 - if #args == 0 then 9 - u.notify("please provide a package url to get", "error") 10 - return 11 - end 12 - 13 - for i, arg in ipairs(args) do 14 - local m = string.match(arg, "^https://(.*)$") or string.match(arg, "^http://(.*)$") or arg 15 - table.remove(args, i) 16 - table.insert(args, i, m) 17 - end 18 - 19 - local cmd_args = vim.list_extend({ "get" }, args) 20 - 21 - Job 22 - :new({ 23 - command = c.go, 24 - args = cmd_args, 25 - on_exit = function(_, retval) 26 - if retval ~= 0 then 27 - u.notify("command 'go " .. unpack(cmd_args) .. "' exited with code " .. retval, "error") 28 - return 29 - end 30 - 31 - u.notify("go get was success runned", "info") 32 - end, 33 - }) 34 - :start() 35 - end
-29
lua/gopher/gomod.lua
··· 1 - local Job = require "plenary.job" 2 - local c = require("gopher.config").config.commands 3 - local u = require "gopher._utils" 4 - 5 - ---run "go mod" 6 - return function(...) 7 - local args = { ... } 8 - if #args == 0 then 9 - u.notify("please provide any mod command", "error") 10 - return 11 - end 12 - 13 - local cmd_args = vim.list_extend({ "mod" }, args) 14 - 15 - Job 16 - :new({ 17 - command = c.go, 18 - args = cmd_args, 19 - on_exit = function(_, retval) 20 - if retval ~= 0 then 21 - u.notify("command 'go " .. unpack(cmd_args) .. "' exited with code " .. retval, "error") 22 - return 23 - end 24 - 25 - u.notify("go mod was success runned", "info") 26 - end, 27 - }) 28 - :start() 29 - end
+58 -55
lua/gopher/gotests.lua
··· 1 - local Job = require "plenary.job" 1 + ---@toc_entry Generating unit tests boilerplate 2 + ---@tag gopher.nvim-gotests 3 + ---@text gotests is utilizing the `gotests` tool to generate unit tests boilerplate. 4 + ---@usage 5 + --- - Generate unit test for specific function/method: 6 + --- 1. Place your cursor on the desired function/method. 7 + --- 2. Run `:GoTestAdd` 8 + --- 9 + --- - Generate unit tests for *all* functions/methods in current file: 10 + --- - run `:GoTestsAll` 11 + --- 12 + --- - Generate unit tests *only* for *exported(public)* functions/methods: 13 + --- - run `:GoTestsExp` 14 + --- 15 + --- You can also specify the template to use for generating the tests. 16 + --- See |gopher.nvim-config|. 17 + --- More details about templates: https://github.com/cweill/gotests 18 + --- 19 + --- If you prefer named tests, you can enable them in |gopher.nvim-config|. 20 + 21 + local c = require "gopher.config" 2 22 local ts_utils = require "gopher._utils.ts" 3 - local c = require("gopher.config").config.commands 23 + local r = require "gopher._utils.runner" 4 24 local u = require "gopher._utils" 5 - local M = {} 25 + local log = require "gopher._utils.log" 26 + local gotests = {} 27 + 28 + ---@param args table 29 + ---@dochide 30 + local function add_test(args) 31 + if c.gotests.named then 32 + table.insert(args, "-named") 33 + end 6 34 7 - ---@param cmd_args table 8 - local function run(cmd_args) 9 - Job 10 - :new({ 11 - command = c.gotests, 12 - args = cmd_args, 13 - on_exit = function(_, retval) 14 - if retval ~= 0 then 15 - u.notify("command 'go " .. unpack(cmd_args) .. "' exited with code " .. retval, "error") 16 - return 17 - end 35 + if c.gotests.template_dir then 36 + table.insert(args, "-template_dir") 37 + table.insert(args, c.gotests.template_dir) 38 + end 18 39 19 - u.notify("unit test(s) generated", "info") 20 - end, 21 - }) 22 - :start() 23 - end 40 + if c.gotests.template ~= "default" then 41 + table.insert(args, "-template") 42 + table.insert(args, c.gotests.template) 43 + end 24 44 25 - ---@param args table 26 - local function add_test(args) 27 - local fpath = vim.fn.expand "%" ---@diagnostic disable-line: missing-parameter 28 45 table.insert(args, "-w") 29 - table.insert(args, fpath) 30 - run(args) 31 - end 46 + table.insert(args, vim.fn.expand "%") 32 47 33 - ---generate unit test for one function 34 - ---@param parallel boolean 35 - function M.func_test(parallel) 36 - local ns = ts_utils.get_func_method_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0))) 37 - if ns == nil or ns.name == nil then 38 - u.notify("cursor on func/method and execute the command again", "info") 39 - return 40 - end 48 + log.debug("generating tests with args: ", args) 41 49 42 - local cmd_args = { "-only", ns.name } 43 - if parallel then 44 - table.insert(cmd_args, "-parallel") 50 + local rs = r.sync { c.commands.gotests, unpack(args) } 51 + if rs.code ~= 0 then 52 + error("gotests failed: " .. rs.stderr) 45 53 end 46 54 47 - add_test(cmd_args) 55 + u.notify "unit test(s) generated" 48 56 end 49 57 50 - ---generate unit tests for all functions in current file 51 - ---@param parallel boolean 52 - function M.all_tests(parallel) 53 - local cmd_args = { "-all" } 54 - if parallel then 55 - table.insert(cmd_args, "-parallel") 56 - end 58 + -- generate unit test for one function 59 + function gotests.func_test() 60 + local bufnr = vim.api.nvim_get_current_buf() 61 + local func = ts_utils.get_func_under_cursor(bufnr) 57 62 58 - add_test(cmd_args) 63 + add_test { "-only", func.name } 59 64 end 60 65 61 - ---generate unit tests for all exported functions 62 - ---@param parallel boolean 63 - function M.all_exported_tests(parallel) 64 - local cmd_args = {} 65 - if parallel then 66 - table.insert(cmd_args, "-parallel") 67 - end 66 + -- generate unit tests for all functions in current file 67 + function gotests.all_tests() 68 + add_test { "-all" } 69 + end 68 70 69 - table.insert(cmd_args, "-exported") 70 - add_test(cmd_args) 71 + -- generate unit tests for all exported functions 72 + function gotests.all_exported_tests() 73 + add_test { "-exported" } 71 74 end 72 75 73 - return M 76 + return gotests
+49 -30
lua/gopher/health.lua
··· 1 - local health = vim.health or require "health" 2 - local utils = require "gopher._utils" 3 - local c = require("gopher.config").config.commands 1 + local c = require("gopher.config").commands 2 + local health = {} 4 3 5 - local M = { 6 - _required = { 7 - plugins = { 8 - { lib = "plenary" }, 9 - { lib = "nvim-treesitter" }, 10 - }, 11 - binarys = { 12 - { bin = c.go, help = "required for GoMod, GoGet, GoGenerate command" }, 13 - { bin = c.gomodifytags, help = "required for modify struct tags" }, 14 - { bin = c.impl, help = "required for interface implementing" }, 15 - { bin = c.gotests, help = "required for test(s) generation" }, 4 + local deps = { 5 + vim_version = "nvim-0.10", 6 + bin = { 7 + { 8 + bin = c.go, 9 + msg = "required for `:GoGet`, `:GoMod`, `:GoGenerate`, `:GoWork`, `:GoInstallDeps`, `:GoInstallDepsSync`", 16 10 }, 11 + { bin = c.gomodifytags, msg = "required for `:GoTagAdd`, `:GoTagRm`" }, 12 + { bin = c.impl, msg = "required for `:GoImpl`" }, 13 + { bin = c.iferr, msg = "required for `:GoIfErr`" }, 14 + { bin = c.gotests, msg = "required for `:GoTestAdd`, `:GoTestsAll`, `:GoTestsExp`" }, 15 + }, 16 + treesitter = { 17 + { parser = "go", msg = "required for most of the parts of `gopher.nvim`" }, 17 18 }, 18 19 } 19 20 20 - function M.check() 21 - health.report_start "Required plugins" 22 - for _, plugin in ipairs(M._required.plugins) do 23 - if utils.lualib_is_found(plugin.lib) then 24 - health.report_ok(plugin.lib .. " installed.") 25 - else 26 - health.report_error(plugin.lib .. " not found. Gopher.nvim will not work without it!") 27 - end 21 + ---@param bin {bin:string, msg:string, optional:boolean} 22 + local function check_binary(bin) 23 + if vim.fn.executable(bin.bin) == 1 then 24 + vim.health.ok(bin.bin .. " is found oh PATH: `" .. vim.fn.exepath(bin.bin) .. "`") 25 + else 26 + vim.health.error(bin.bin .. " not found on PATH, " .. bin.msg) 27 + end 28 + end 29 + 30 + ---@param ts {parser:string, msg:string} 31 + local function check_treesitter(ts) 32 + local ok, parser = pcall(vim.treesitter.get_parser, 0, ts.parser) 33 + if ok and parser ~= nil then 34 + vim.health.ok("`" .. ts.parser .. "` parser is installed") 35 + else 36 + vim.health.error("`" .. ts.parser .. "` parser not found") 37 + end 38 + end 39 + 40 + function health.check() 41 + vim.health.start "Neovim version" 42 + if vim.fn.has(deps.vim_version) == 1 then 43 + vim.health.ok "Neovim version is compatible" 44 + else 45 + vim.health.error(deps.vim_version .. " or newer is required") 46 + end 47 + 48 + vim.health.start "Required binaries (those can be installed with `:GoInstallDeps`)" 49 + for _, bin in ipairs(deps.bin) do 50 + check_binary(bin) 28 51 end 29 52 30 - health.report_start "Required go tools" 31 - for _, binary in ipairs(M._required.binarys) do 32 - if utils.binary_is_found(binary.bin) then 33 - health.report_ok(binary.bin .. " installed") 34 - else 35 - health.report_warn(binary.bin .. " is not installed but " .. binary.help) 36 - end 53 + vim.health.start "Treesitter" 54 + for _, parser in ipairs(deps.treesitter) do 55 + check_treesitter(parser) 37 56 end 38 57 end 39 58 40 - return M 59 + return health
+48
lua/gopher/iferr.lua
··· 1 + -- Thanks https://github.com/koron/iferr for vim implementation 2 + 3 + ---@toc_entry Iferr 4 + ---@tag gopher.nvim-iferr 5 + ---@text 6 + --- `iferr` provides a way to way to automatically insert `if err != nil` check. 7 + --- If you want to change `-message` option of `iferr` tool, see |gopher.nvim-config| 8 + --- 9 + ---@usage Execute `:GoIfErr` near any `err` variable to insert the check 10 + 11 + local c = require "gopher.config" 12 + local u = require "gopher._utils" 13 + local r = require "gopher._utils.runner" 14 + local log = require "gopher._utils.log" 15 + local iferr = {} 16 + 17 + function iferr.iferr() 18 + local curb = vim.fn.wordcount().cursor_bytes 19 + local pos = vim.fn.getcurpos()[2] 20 + local fpath = vim.fn.expand "%" 21 + 22 + local cmd = { c.commands.iferr, "-pos", curb } 23 + if c.iferr.message ~= nil and type(c.iferr.message) == "string" then 24 + table.insert(cmd, "-message") 25 + table.insert(cmd, c.iferr.message) 26 + end 27 + 28 + local rs = r.sync(cmd, { 29 + stdin = u.readfile_joined(fpath), 30 + }) 31 + 32 + if rs.code ~= 0 then 33 + if string.find(rs.stderr, "no functions at") then 34 + u.notify("iferr: no function at " .. curb, vim.log.levels.ERROR) 35 + log.warn("iferr: no function at " .. curb) 36 + return 37 + end 38 + 39 + log.error("ferr: failed. output: " .. rs.stderr) 40 + error("iferr failed: " .. rs.stderr) 41 + end 42 + 43 + vim.fn.append(pos, u.remove_empty_lines(vim.split(rs.stdout, "\n"))) 44 + vim.cmd [[silent normal! j=2j]] 45 + vim.fn.setpos(".", pos --[[@as integer[] ]]) 46 + end 47 + 48 + return iferr
+66 -61
lua/gopher/impl.lua
··· 1 - local Job = require "plenary.job" 1 + ---@toc_entry Auto implementation of interface methods 2 + ---@tag gopher.nvim-impl 3 + ---@text 4 + --- Integration of `impl` tool to generate method stubs for interfaces. 5 + --- 6 + ---@usage 7 + --- 1. Automatically implement an interface for a struct: 8 + --- - Place your cursor on the struct where you want to implement the interface. 9 + --- - Run `:GoImpl io.Reader` 10 + --- - This will automatically determine the receiver and implement the `io.Reader` interface. 11 + --- 12 + --- 2. Specify a custom receiver: 13 + --- - Place your cursor on the struct 14 + --- - Run `:GoImpl w io.Writer`, where: 15 + --- - `w` is the receiver. 16 + --- - `io.Writer` is the interface to implement. 17 + --- 18 + --- 3. Explicitly specify the receiver, struct, and interface: 19 + --- - No need to place the cursor on the struct if all arguments are provided. 20 + --- - Run `:GoImpl r RequestReader io.Reader`, where: 21 + --- - `r` is the receiver. 22 + --- - `RequestReader` is the struct. 23 + --- - `io.Reader` is the interface to implement. 24 + --- 25 + --- Example: 26 + --- >go 27 + --- type BytesReader struct{} 28 + --- // ^ put your cursor here 29 + --- // run `:GoImpl b io.Reader` 30 + --- 31 + --- // this is what you will get 32 + --- func (b *BytesReader) Read(p []byte) (n int, err error) { 33 + --- panic("not implemented") // TODO: Implement 34 + --- } 35 + --- < 36 + 37 + local c = require("gopher.config").commands 38 + local r = require "gopher._utils.runner" 2 39 local ts_utils = require "gopher._utils.ts" 3 - local c = require("gopher.config").config.commands 4 40 local u = require "gopher._utils" 5 - 6 - ---@return string 7 - local function get_struct() 8 - local ns = ts_utils.get_struct_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0))) 9 - if ns == nil then 10 - u.notify("put cursor on a struct or specify a receiver", "info") 11 - return "" 12 - end 13 - 14 - vim.api.nvim_win_set_cursor(0, { 15 - ns.dim.e.r, 16 - ns.dim.e.c, 17 - }) 18 - 19 - return ns.name 20 - end 41 + local impl = {} 21 42 22 - return function(...) 43 + function impl.impl(...) 23 44 local args = { ... } 24 - local iface, recv_name = "", "" 25 - local recv = get_struct() 45 + local iface, recv = "", "" 46 + local bufnr = vim.api.nvim_get_current_buf() 26 47 27 48 if #args == 0 then 28 - iface = vim.fn.input "impl: generating method stubs for interface: " 29 - vim.cmd "redeaw!" 30 - if iface == "" then 31 - u.notify("usage: GoImpl f *File io.Reader", "info") 32 - return 33 - end 49 + u.notify("arguments not provided. usage: :GoImpl f *File io.Reader", vim.log.levels.ERROR) 50 + return 34 51 elseif #args == 1 then -- :GoImpl io.Reader 35 - recv = string.lower(recv) .. " *" .. recv 36 - vim.cmd "redraw!" 37 - iface = select(1, ...) 52 + local st = ts_utils.get_struct_under_cursor(bufnr) 53 + iface = args[1] 54 + recv = string.lower(st.name) .. " *" .. st.name 38 55 elseif #args == 2 then -- :GoImpl w io.Writer 39 - recv_name = select(1, ...) 40 - recv = string.format("%s *%s", recv_name, recv) 41 - iface = select(#args, ...) 42 - elseif #args > 2 then 43 - iface = select(#args, ...) 44 - recv = select(#args - 1, ...) 45 - recv_name = select(#args - 2, ...) 46 - recv = string.format("%s %s", recv_name, recv) 56 + local st = ts_utils.get_struct_under_cursor(bufnr) 57 + iface = args[2] 58 + recv = args[1] .. " *" .. st.name 59 + elseif #args == 3 then -- :GoImpl r Struct io.Reader 60 + recv = args[1] .. " *" .. args[2] 61 + iface = args[3] 47 62 end 48 63 49 - -- stylua: ignore 50 - local cmd_args = { 51 - "-dir", vim.fn.fnameescape(vim.fn.expand "%:p:h"), ---@diagnostic disable-line: missing-parameter 52 - recv, 53 - iface 54 - } 64 + assert(iface ~= "", "interface not provided") 65 + assert(recv ~= "", "receiver not provided") 55 66 56 - local res_data 57 - Job 58 - :new({ 59 - command = c.impl, 60 - args = cmd_args, 61 - on_exit = function(data, retval) 62 - if retval ~= 0 then 63 - u.notify("command 'impl " .. unpack(cmd_args) .. "' exited with code " .. retval, "error") 64 - return 65 - end 67 + local dir = vim.fn.fnameescape(vim.fn.expand "%:p:h") 68 + local rs = r.sync { c.impl, "-dir", dir, recv, iface } 69 + if rs.code ~= 0 then 70 + error("failed to implement interface: " .. rs.stderr) 71 + end 66 72 67 - res_data = data:result() 68 - end, 69 - }) 70 - :sync() 73 + local pos = vim.fn.getcurpos()[2] 74 + local output = u.remove_empty_lines(vim.split(rs.stdout, "\n")) 71 75 72 - local pos = vim.fn.getcurpos()[2] 73 - table.insert(res_data, 1, "") 74 - vim.fn.append(pos, res_data) 76 + table.insert(output, 1, "") 77 + vim.fn.append(pos, output) 75 78 end 79 + 80 + return impl
+61 -13
lua/gopher/init.lua
··· 1 + --- *gopher.nvim* Enhance your golang experience 2 + --- 3 + --- MIT License Copyright (c) 2025 Oleksandr Smirnov 4 + --- 5 + --- ============================================================================== 6 + --- 7 + --- gopher.nvim is a minimalistic plugin for Go development in Neovim written in Lua. 8 + --- It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim. 9 + --- 10 + --- Table of Contents 11 + ---@toc 12 + 13 + local log = require "gopher._utils.log" 1 14 local tags = require "gopher.struct_tags" 2 - local gotests = require "gopher.gotests" 15 + local tests = require "gopher.gotests" 16 + local go = require "gopher.go" 3 17 local gopher = {} 4 18 5 - gopher.install_deps = require "gopher.installer" 6 - gopher.tags_add = tags.add 7 - gopher.tags_rm = tags.remove 8 - gopher.mod = require "gopher.gomod" 9 - gopher.get = require "gopher.goget" 10 - gopher.impl = require "gopher.impl" 11 - gopher.generate = require "gopher.gogenerate" 12 - gopher.comment = require "gopher.comment" 13 - gopher.test_add = gotests.func_test 14 - gopher.test_exported = gotests.all_exported_tests 15 - gopher.tests_all = gotests.all_tests 16 - gopher.setup = require("gopher.config").setup 19 + ---@toc_entry Setup 20 + ---@tag gopher.nvim-setup() 21 + ---@text Setup function. This method simply merges default config with opts table. 22 + --- You can read more about configuration at |gopher.nvim-config| 23 + --- Calling this function is optional, if you ok with default settings. 24 + --- See |gopher.nvim.config| 25 + --- 26 + ---@usage >lua 27 + --- require("gopher").setup {} -- use default config or replace {} with your own 28 + --- < 29 + ---@param user_config gopher.Config See |gopher.nvim-config| 30 + gopher.setup = function(user_config) 31 + log.debug "setting up config" 32 + require("gopher.config").setup(user_config) 33 + log.debug(vim.inspect(user_config)) 34 + end 35 + 36 + ---@toc_entry Install dependencies 37 + ---@tag gopher.nvim-dependencies 38 + ---@text 39 + --- Gopher.nvim implements most of its features using third-party tools. To 40 + --- install plugin's dependencies, you can run: 41 + --- `:GoInstallDeps` or `:GoInstallDepsSync` 42 + --- or use `require("gopher").install_deps()` if you prefer lua api. 43 + gopher.install_deps = require("gopher.installer").install_deps 44 + 45 + gopher.impl = require("gopher.impl").impl 46 + gopher.iferr = require("gopher.iferr").iferr 47 + gopher.comment = require("gopher.comment").comment 48 + 49 + gopher.tags = { 50 + add = tags.add, 51 + rm = tags.remove, 52 + clear = tags.clear, 53 + } 54 + 55 + gopher.test = { 56 + add = tests.func_test, 57 + exported = tests.all_exported_tests, 58 + all = tests.all_tests, 59 + } 60 + 61 + gopher.get = go.get 62 + gopher.mod = go.mod 63 + gopher.work = go.work 64 + gopher.generate = go.generate 17 65 18 66 return gopher
+55 -23
lua/gopher/installer.lua
··· 1 - local Job = require "plenary.job" 1 + local c = require "gopher.config" 2 + local r = require "gopher._utils.runner" 2 3 local u = require "gopher._utils" 4 + local log = require "gopher._utils.log" 5 + local installer = {} 6 + 3 7 local urls = { 4 - gomodifytags = "github.com/fatih/gomodifytags", 5 - impl = "github.com/josharian/impl", 6 - gotests = "github.com/cweill/gotests/...", 8 + gomodifytags = "github.com/fatih/gomodifytags@latest", 9 + impl = "github.com/josharian/impl@latest", 10 + gotests = "github.com/cweill/gotests/...@develop", 11 + iferr = "github.com/koron/iferr@latest", 12 + json2go = "olexsmir.xyz/json2go/cmd/json2go@latest", 7 13 } 8 14 9 - ---@param pkg string 10 - local function install(pkg) 11 - local url = urls[pkg] .. "@latest" 15 + ---@param opt vim.SystemCompleted 16 + ---@param url string 17 + local function handle_intall_exit(opt, url) 18 + if opt.code ~= 0 then 19 + vim.schedule(function() 20 + u.notify("go install failed: " .. url) 21 + end) 12 22 13 - Job 14 - :new({ 15 - command = "go", 16 - args = { "install", url }, 17 - on_exit = function(_, retval) 18 - if retval ~= 0 then 19 - u.notify("command 'go install " .. url .. "' exited with code " .. retval, "error") 20 - return 21 - end 23 + log.error("go install failed:", "url", url, "opt", vim.inspect(opt)) 24 + return 25 + end 22 26 23 - u.notify("install " .. url .. " finished", "info ") 24 - end, 25 - }) 26 - :start() 27 + vim.schedule(function() 28 + u.notify("go install-ed: " .. url) 29 + end) 30 + end 31 + 32 + ---@param url string 33 + local function install(url) 34 + vim.schedule(function() 35 + u.notify("go install-ing: " .. url) 36 + end) 37 + 38 + r.async({ c.commands.go, "install", url }, function(opt) 39 + handle_intall_exit(opt, url) 40 + end, { timeout = c.installer_timeout }) 41 + end 42 + 43 + ---@param url string 44 + local function install_sync(url) 45 + vim.schedule(function() 46 + u.notify("go install-ing: " .. url) 47 + end) 48 + 49 + local rs = r.sync({ c.commands.go, "install", url }, { timeout = c.installer_timeout }) 50 + handle_intall_exit(rs, url) 27 51 end 28 52 29 53 ---Install required go deps 30 - return function() 31 - for pkg, _ in pairs(urls) do 32 - install(pkg) 54 + ---@param opts? {sync:boolean} 55 + function installer.install_deps(opts) 56 + opts = opts or {} 57 + for _, url in pairs(urls) do 58 + if opts.sync then 59 + install_sync(url) 60 + else 61 + install(url) 62 + end 33 63 end 34 64 end 65 + 66 + return installer
+137
lua/gopher/json2go.lua
··· 1 + ---@toc_entry json2go 2 + ---@tag gopher.nvim-json2go 3 + ---@text 4 + --- Convert json to go type annotations. 5 + --- 6 + ---@usage 7 + --- `:GoJson` opens a temporary buffer where you can paste or write JSON. 8 + --- Saving the buffer (`:w` or `:wq`) automatically closes it and inserts the 9 + --- generated Go struct into the original buffer at the cursor position. 10 + --- 11 + --- Alternatively, you can pass JSON directly as an argument: 12 + --- >vim 13 + --- :GoJson {"name": "Alice", "age": 30} 14 + --- < 15 + 16 + local c = require "gopher.config" 17 + local log = require "gopher._utils.log" 18 + local u = require "gopher._utils" 19 + local r = require "gopher._utils.runner" 20 + local json2go = {} 21 + 22 + ---@dochide 23 + ---@param bufnr integer 24 + ---@param cpos integer 25 + ---@param type_ string 26 + local function apply(bufnr, cpos, type_) 27 + local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) 28 + local input_lines = u.remove_empty_lines(vim.split(type_, "\n")) 29 + for i, line in pairs(input_lines) do 30 + table.insert(lines, cpos + i, line) 31 + end 32 + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines) 33 + end 34 + 35 + -- Convert json string to go type. 36 + --- 37 + ---@param json_str string Json string that is going to be converted to go type. 38 + ---@return string? Go type, or nil if failed. 39 + function json2go.transform(json_str) 40 + local cmd = { c.commands.json2go } 41 + if c.json2go.type_name then 42 + table.insert(cmd, "-type", c.json2go.type_name) 43 + end 44 + 45 + local rs = r.sync(cmd, { stdin = json_str }) 46 + if rs.code ~= 0 then 47 + u.notify("json2go: got this error: " .. rs.stdout, vim.log.levels.ERROR) 48 + log.error("json2go: got this error: " .. rs.stdout) 49 + return 50 + end 51 + return rs.stdout 52 + end 53 + 54 + ---@dochide 55 + ---@param ocpos integer 56 + local function interactive(ocpos) 57 + local obuf = vim.api.nvim_get_current_buf() 58 + local owin = vim.api.nvim_get_current_win() 59 + 60 + -- setup the input window 61 + local buf = vim.api.nvim_create_buf(false, true) 62 + vim.cmd(c.json2go.interactive_cmd) 63 + 64 + local win = vim.api.nvim_get_current_win() 65 + vim.api.nvim_win_set_buf(win, buf) 66 + vim.api.nvim_buf_set_name(buf, "[GoJson input]") 67 + vim.api.nvim_set_option_value("filetype", "jsonc", { buf = buf }) 68 + vim.api.nvim_set_option_value("buftype", "acwrite", { buf = buf }) 69 + vim.api.nvim_set_option_value("swapfile", false, { buf = buf }) 70 + vim.api.nvim_set_option_value("bufhidden", "delete", { buf = buf }) 71 + vim.api.nvim_buf_set_lines(buf, 0, -1, false, { 72 + "// Write your json here.", 73 + "// Writing and quitting (:wq), will generate go struct under the cursor.", 74 + "", 75 + "", 76 + }) 77 + 78 + vim.api.nvim_create_autocmd("BufLeave", { buffer = buf, command = "stopinsert" }) 79 + vim.api.nvim_create_autocmd("BufWriteCmd", { 80 + buffer = buf, 81 + once = true, 82 + callback = function() 83 + local input = vim.api.nvim_buf_get_lines(buf, 0, -1, true) 84 + local inp = table.concat( 85 + vim 86 + .iter(input) 87 + :filter(function(line) 88 + local found = string.find(line, "^//.*") 89 + return (not found) or (line == "") 90 + end) 91 + :totable(), 92 + "\n" 93 + ) 94 + 95 + local go_type = json2go.transform(inp) 96 + if not go_type then 97 + error "cound't convert json to go type" 98 + end 99 + 100 + vim.api.nvim_set_option_value("modified", false, { buf = buf }) 101 + apply(obuf, ocpos, go_type) 102 + 103 + vim.api.nvim_set_current_win(owin) 104 + vim.api.nvim_win_set_cursor(owin, { ocpos + 1, 0 }) 105 + 106 + vim.schedule(function() 107 + pcall(vim.api.nvim_win_close, win, true) 108 + pcall(vim.api.nvim_buf_delete, buf, {}) 109 + end) 110 + end, 111 + }) 112 + 113 + vim.cmd "normal! G" 114 + vim.cmd "startinsert" 115 + end 116 + 117 + --- Converts json string to go type, and puts result under the cursor. If 118 + --- [json_str] is nil, will open an interactive prompt (with cmd set in 119 + --- config). 120 + --- 121 + ---@param json_str? string 122 + function json2go.json2go(json_str) 123 + local cur_line = vim.api.nvim_win_get_cursor(0)[1] 124 + if not json_str then 125 + interactive(cur_line) 126 + return 127 + end 128 + 129 + local go_type = json2go.transform(json_str) 130 + if not go_type then 131 + error "cound't convert json to go type" 132 + end 133 + 134 + apply(0, cur_line, go_type) 135 + end 136 + 137 + return json2go
+167 -87
lua/gopher/struct_tags.lua
··· 1 - local Job = require "plenary.job" 2 - local ts_utils = require "gopher._utils.ts" 1 + ---@toc_entry Modify struct tags 2 + ---@tag gopher.nvim-struct-tags 3 + ---@text 4 + --- `struct_tags` is utilizing the `gomodifytags` tool to add or remove tags to 5 + --- struct fields. 6 + --- 7 + ---@usage 8 + --- How to add/remove/clear tags to struct fields: 9 + --- 1. Place cursor on the struct 10 + --- 2. Run `:GoTagAdd json` to add json tags to struct fields 11 + --- 3. Run `:GoTagRm json` to remove json tags to struct fields 12 + --- 4. Run `:GoTagClear` to clear all tags from struct fields 13 + --- 14 + --- If you want to add/remove tag with options, you can use `json=omitempty` 15 + --- (where json is tag, and omitempty is its option). 16 + --- Example: `:GoTagAdd xml json=omitempty` 17 + --- 18 + --- 19 + --- NOTE: if you dont specify the tag it will use `json` as default 20 + --- 21 + --- Example: 22 + --- >go 23 + --- // before 24 + --- type User struct { 25 + --- // ^ put your cursor here 26 + --- // run `:GoTagAdd yaml` 27 + --- ID int 28 + --- Name string 29 + --- } 30 + --- 31 + --- // after 32 + --- type User struct { 33 + --- ID int `yaml:id` 34 + --- Name string `yaml:name` 35 + --- } 36 + --- < 37 + 38 + local ts = require "gopher._utils.ts" 39 + local r = require "gopher._utils.runner" 40 + local c = require "gopher.config" 3 41 local u = require "gopher._utils" 4 - local c = require("gopher.config").config.commands 5 - local M = {} 42 + local log = require "gopher._utils.log" 43 + local struct_tags = {} 6 44 7 - local function modify(...) 8 - local fpath = vim.fn.expand "%" ---@diagnostic disable-line: missing-parameter 9 - local ns = ts_utils.get_struct_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0))) 10 - if ns == nil or ns == {} then 11 - return 12 - end 45 + ---@dochide 46 + ---@class gopher.StructTagInput 47 + ---@field input string[] User provided tags 48 + ---@field range? gopher.StructTagRange (optional) 49 + 50 + ---@dochide 51 + ---@class gopher.StructTagRange 52 + ---@field start number 53 + ---@field end_ number 54 + 55 + ---@param fpath string 56 + ---@param bufnr integer 57 + ---@param range? gopher.StructTagRange 58 + ---@param user_args string[] 59 + ---@dochide 60 + local function handle_tags(fpath, bufnr, range, user_args) 61 + local st = ts.get_struct_under_cursor(bufnr) 13 62 14 63 -- stylua: ignore 15 - local cmd_args = { 64 + local cmd = { 65 + c.commands.gomodifytags, 66 + "-transform", c.gotag.transform, 16 67 "-format", "json", 17 68 "-file", fpath, 18 - "-w" 69 + "-w", 19 70 } 20 71 21 - -- by struct name of line pos 22 - if ns.name == nil then 23 - local _, csrow, _, _ = unpack(vim.fn.getpos ".") 24 - table.insert(cmd_args, "-line") 25 - table.insert(cmd_args, csrow) 72 + -- `-struct` and `-line` cannot be combined, setting them separately 73 + if range or st.is_varstruct then 74 + table.insert(cmd, "-line") 75 + table.insert(cmd, string.format("%d,%d", (range or st).start, (range or st).end_)) 26 76 else 27 - table.insert(cmd_args, "-struct") 28 - table.insert(cmd_args, ns.name) 77 + table.insert(cmd, "-struct") 78 + table.insert(cmd, st.name) 29 79 end 30 80 31 - -- set user args for cmd 32 - local arg = { ... } 33 - for _, v in ipairs(arg) do 34 - table.insert(cmd_args, v) 81 + for _, v in ipairs(user_args) do 82 + table.insert(cmd, v) 35 83 end 36 84 37 - -- set default tag for "clear tags" 38 - if #arg == 1 and arg[1] ~= "-clear-tags" then 39 - table.insert(cmd_args, "json") 85 + local rs = r.sync(cmd) 86 + if rs.code ~= 0 then 87 + log.error("tags: failed to set tags " .. rs.stderr) 88 + error("failed to set tags " .. rs.stderr) 40 89 end 41 90 42 - -- get result of "gomodifytags" works 43 - local res_data 44 - Job 45 - :new({ 46 - command = c.gomodifytags, 47 - args = cmd_args, 48 - on_exit = function(data, retval) 49 - if retval ~= 0 then 50 - u.notify( 51 - "command 'gomodifytags " .. unpack(cmd_args) .. "' exited with code " .. retval, 52 - "error" 53 - ) 54 - return 55 - end 56 - 57 - res_data = data:result() 58 - end, 59 - }) 60 - :sync() 61 - 62 - -- decode goted value 63 - local tagged = vim.json.decode(table.concat(res_data)) 64 - if 65 - tagged.errors ~= nil 66 - or tagged.lines == nil 67 - or tagged["start"] == nil 68 - or tagged["start"] == 0 69 - then 70 - u.notify("failed to set tags " .. vim.inspect(tagged), "error") 91 + local res = vim.json.decode(rs.stdout) 92 + if res["errors"] then 93 + log.error("tags: got an error " .. vim.inspect(res)) 94 + error("failed to set tags " .. vim.inspect(res["errors"])) 71 95 end 72 96 73 - for i, v in ipairs(tagged.lines) do 74 - tagged.lines[i] = u.rtrim(v) 97 + for i, v in ipairs(res["lines"]) do 98 + res["lines"][i] = u.trimend(v) 75 99 end 76 100 77 - -- write goted tags 78 101 vim.api.nvim_buf_set_lines( 79 - 0, 80 - tagged.start - 1, 81 - tagged.start - 1 + #tagged.lines, 82 - false, 83 - tagged.lines 102 + bufnr, 103 + res["start"] - 1, 104 + res["start"] - 1 + #res["lines"], 105 + true, 106 + res["lines"] 84 107 ) 85 - vim.cmd "write" 86 108 end 87 109 88 - ---add tags to struct under cursor 89 - ---@param ... unknown 90 - function M.add(...) 91 - local arg = { ... } 92 - if #arg == nil or arg == "" then 93 - arg = { "json" } 110 + ---@dochide 111 + ---@param option string 112 + local function option_to_tag(option) 113 + return option:match "^(.-)=" 114 + end 115 + 116 + ---@dochide 117 + ---@param args string[] 118 + local function unwrap_if_needed(args) 119 + local out = {} 120 + for _, v in pairs(args) do 121 + for _, p in pairs(vim.split(v, ",")) do 122 + table.insert(out, p) 123 + end 94 124 end 125 + return out 126 + end 95 127 96 - local cmd_args = { "-add-tags" } 97 - for _, v in ipairs(arg) do 98 - table.insert(cmd_args, v) 128 + ---@dochide 129 + ---@class gopher.StructTagsArgs 130 + ---@field tags string 131 + ---@field options string 132 + 133 + ---@dochide 134 + ---@param args string[] 135 + ---@return gopher.StructTagsArgs 136 + function struct_tags.parse_args(args) 137 + args = unwrap_if_needed(args) 138 + 139 + local tags, options = {}, {} 140 + for _, v in pairs(args) do 141 + if string.find(v, "=") then 142 + table.insert(options, v) 143 + table.insert(tags, option_to_tag(v)) 144 + else 145 + table.insert(tags, v) 146 + end 99 147 end 100 148 101 - modify(unpack(cmd_args)) 149 + return { 150 + tags = table.concat(u.list_unique(tags), ","), 151 + options = table.concat(u.list_unique(options), ","), 152 + } 102 153 end 103 154 104 - ---remove tags to struct under cursor 105 - ---@param ... unknown 106 - function M.remove(...) 107 - local arg = { ... } 108 - if #arg == nil or arg == "" then 109 - arg = { "json" } 110 - end 155 + -- Adds tags to a struct under the cursor. See `:h gopher.nvim-struct-tags`. 156 + ---@param opts gopher.StructTagInput 157 + ---@dochide 158 + function struct_tags.add(opts) 159 + log.debug("adding tags", opts) 160 + 161 + local fpath = vim.fn.expand "%" 162 + local bufnr = vim.api.nvim_get_current_buf() 163 + 164 + local user_args = struct_tags.parse_args(opts.input) 165 + handle_tags(fpath, bufnr, opts.range, { 166 + "-add-tags", 167 + (user_args.tags ~= "") and user_args.tags or c.gotag.default_tag, 168 + (user_args.options ~= "" or c.gotag.option) and "-add-options" or nil, 169 + (user_args.options ~= "") and user_args.options or c.gotag.option, 170 + }) 171 + end 172 + 173 + -- Removes tags from a struct under the cursor. See `:h gopher.nvim-struct-tags`. 174 + ---@dochide 175 + ---@param opts gopher.StructTagInput 176 + function struct_tags.remove(opts) 177 + log.debug("removing tags", opts) 178 + 179 + local fpath = vim.fn.expand "%" 180 + local bufnr = vim.api.nvim_get_current_buf() 111 181 112 - local cmd_args = { "-remove-tags" } 113 - for _, v in ipairs(arg) do 114 - table.insert(cmd_args, v) 115 - end 182 + local user_args = struct_tags.parse_args(opts.input) 183 + handle_tags(fpath, bufnr, opts.range, { 184 + "-remove-tags", 185 + (user_args.tags ~= "") and user_args.tags or c.gotag.default_tag, 186 + (user_args.options ~= "" or c.gotag.option ~= nil) and "-remove-options" or nil, 187 + (user_args.options ~= "") and user_args.options or c.gotag.option, 188 + }) 189 + end 116 190 117 - modify(unpack(cmd_args)) 191 + -- Removes all tags from a struct under the cursor. 192 + -- See `:h gopher.nvim-struct-tags`. 193 + ---@dochide 194 + function struct_tags.clear() 195 + local fpath = vim.fn.expand "%" 196 + local bufnr = vim.api.nvim_get_current_buf() 197 + handle_tags(fpath, bufnr, nil, { "-clear-tags" }) 118 198 end 119 199 120 - return M 200 + return struct_tags
+1 -38
nvim.toml
··· 1 1 [vim] 2 2 any = true 3 3 4 - [describe] 5 - any = true 6 - [[describe.args]] 7 - type = "string" 8 - [[describe.args]] 9 - type = "function" 10 - 11 - [it] 12 - any = true 13 - [[it.args]] 14 - type = "string" 15 - [[it.args]] 16 - type = "function" 17 - 18 - [before_each] 4 + [MiniTest] 19 5 any = true 20 - [[before_each.args]] 21 - type = "function" 22 - [[after_each.args]] 23 - type = "function" 24 - 25 - [assert] 26 - any = true 27 - 28 - [assert.is_not] 29 - any = true 30 - 31 - [[assert.equals.args]] 32 - type = "any" 33 - [[assert.equals.args]] 34 - type = "any" 35 - [[assert.equals.args]] 36 - type = "any" 37 - required = false 38 - 39 - [[assert.same.args]] 40 - type = "any" 41 - [[assert.same.args]] 42 - type = "any"
+10
pkg.json
··· 1 + { 2 + "name": "gopher.nvim", 3 + "engines": { 4 + "nvim": "^0.10.0" 5 + }, 6 + "repository": { 7 + "type": "git", 8 + "url": "https://github.com/olexsmir/gopher.nvim" 9 + } 10 + }
+107
plugin/gopher.lua
··· 1 + ---@toc_entry Commands 2 + ---@tag gopher.nvim-commands 3 + ---@text 4 + --- If don't want to automatically register plugins' commands, 5 + --- you can set `vim.g.gopher_register_commands` to `false`, before loading the plugin. 6 + 7 + if vim.g.gopher_register_commands == false then 8 + return 9 + end 10 + 11 + ---@param name string 12 + ---@param fn fun(args: table) 13 + ---@param nargs? number|"*"|"?" 14 + ---@param range? boolean 15 + ---@private 16 + local function cmd(name, fn, nargs, range) 17 + vim.api.nvim_create_user_command(name, fn, { 18 + nargs = nargs or 0, 19 + range = range or false, 20 + }) 21 + end 22 + 23 + cmd("GopherLog", function() 24 + vim.cmd("tabnew " .. require("gopher._utils.log").get_outfile()) 25 + end) 26 + 27 + cmd("GoIfErr", function() 28 + require("gopher").iferr() 29 + end) 30 + 31 + cmd("GoCmt", function() 32 + require("gopher").comment() 33 + end) 34 + 35 + cmd("GoImpl", function(args) 36 + require("gopher").impl(unpack(args.fargs)) 37 + end, "*") 38 + 39 + -- :GoInstall 40 + cmd("GoInstallDeps", function() 41 + require("gopher").install_deps() 42 + end) 43 + 44 + cmd("GoInstallDepsSync", function() 45 + require("gopher").install_deps { sync = true } 46 + end) 47 + 48 + -- :GoTag 49 + cmd("GoTagAdd", function(opts) 50 + require("gopher").tags.add { 51 + input = opts.fargs, 52 + range = (opts.count ~= -1) and { 53 + start = opts.line1, 54 + end_ = opts.line2, 55 + } or nil, 56 + } 57 + end, "*", true) 58 + 59 + cmd("GoTagRm", function(opts) 60 + require("gopher").tags.rm { 61 + input = opts.fargs, 62 + range = (opts.count ~= -1) and { 63 + start = opts.line1, 64 + end_ = opts.line2, 65 + } or nil, 66 + } 67 + end, "*", true) 68 + 69 + cmd("GoTagClear", function() 70 + require("gopher").tags.clear() 71 + end) 72 + 73 + -- :GoJson 74 + cmd("GoJson", function(opts) 75 + local inp = ((opts.args ~= "" and opts.args) or nil) 76 + require("gopher.json2go").json2go(inp) 77 + end, "*") 78 + 79 + -- :GoTest 80 + cmd("GoTestAdd", function() 81 + require("gopher").test.add() 82 + end) 83 + 84 + cmd("GoTestsAll", function() 85 + require("gopher").test.all() 86 + end) 87 + 88 + cmd("GoTestsExp", function() 89 + require("gopher").test.exported() 90 + end) 91 + 92 + -- :Go 93 + cmd("GoMod", function(opts) 94 + require("gopher").mod(opts.fargs) 95 + end, "*") 96 + 97 + cmd("GoGet", function(opts) 98 + require("gopher").get(opts.fargs) 99 + end, "*") 100 + 101 + cmd("GoWork", function(opts) 102 + require("gopher").get(opts.fargs) 103 + end, "*") 104 + 105 + cmd("GoGenerate", function(opts) 106 + require("gopher").generate(opts.fargs or { "" }) 107 + end, "?")
-11
plugin/gopher.vim
··· 1 - command! -nargs=* GoTagAdd :lua require"gopher".tags_add(<f-args>) 2 - command! -nargs=* GoTagRm :lua require"gopher".tags_rm(<f-args>) 3 - command! -nargs=* GoTestAdd :lua require"gopher".test_add(<f-args>) 4 - command! -nargs=* GoTestsAll :lua require"gopher".tests_all(<f-args>) 5 - command! -nargs=* GoTestsExp :lua require"gopher".test_exported(<f-args>) 6 - command! -nargs=* GoMod :lua require"gopher".mod(<f-args>) 7 - command! -nargs=* GoGet :lua require"gopher".get(<f-args>) 8 - command! -nargs=* GoImpl :lua require"gopher".impl(<f-args>) 9 - command! -nargs=* GoGenerate :lua require"gopher".generate(<f-args>) 10 - command! GoCmt :lua require"gopher".comment() 11 - command! GoInstallDeps :lua require"gopher".install_deps()
+38
scripts/docgen.lua
··· 1 + ---@diagnostic disable: undefined-global 2 + --# selene: allow(undefined_variable) 3 + 4 + local okay, minidoc = pcall(require, "mini.doc") 5 + if not okay then 6 + error "mini.doc not found, please install it. https://github.com/echasnovski/mini.doc" 7 + return 8 + end 9 + 10 + local files = { 11 + "lua/gopher/init.lua", 12 + "lua/gopher/config.lua", 13 + "plugin/gopher.lua", 14 + "lua/gopher/struct_tags.lua", 15 + "lua/gopher/json2go.lua", 16 + "lua/gopher/impl.lua", 17 + "lua/gopher/gotests.lua", 18 + "lua/gopher/iferr.lua", 19 + "lua/gopher/comment.lua", 20 + } 21 + 22 + minidoc.setup() 23 + 24 + local hooks = vim.deepcopy(minidoc.default_hooks) 25 + hooks.write_pre = function(lines) 26 + -- Remove first two lines with `======` and `------` delimiters to comply 27 + -- with `:h local-additions` template 28 + table.remove(lines, 1) 29 + table.remove(lines, 1) 30 + 31 + return lines 32 + end 33 + 34 + hooks.sections["@dochide"] = function(s) 35 + s.parent:clear_lines() 36 + end 37 + 38 + MiniDoc.generate(files, "doc/gopher.nvim.txt", { hooks = hooks })
+72
scripts/minimal_init.lua
··· 1 + local function root(p) 2 + local f = debug.getinfo(1, "S").source:sub(2) 3 + return vim.fn.fnamemodify(f, ":p:h:h") .. "/" .. (p or "") 4 + end 5 + 6 + local function install_plug(plugin) 7 + local name = plugin:match ".*/(.*)" 8 + local package_root = root ".tests/site/pack/deps/start/" 9 + if not vim.uv.fs_stat(package_root .. name) then 10 + print("Installing " .. plugin) 11 + vim 12 + .system({ 13 + "git", 14 + "clone", 15 + "--depth=1", 16 + "https://github.com/" .. plugin .. ".git", 17 + package_root .. "/" .. name, 18 + }) 19 + :wait() 20 + end 21 + end 22 + 23 + install_plug "nvim-treesitter/nvim-treesitter" 24 + install_plug "echasnovski/mini.doc" -- used for docs generation 25 + install_plug "folke/tokyonight.nvim" -- theme for generating demos 26 + install_plug "echasnovski/mini.test" 27 + 28 + vim.env.XDG_CONFIG_HOME = root ".tests/config" 29 + vim.env.XDG_DATA_HOME = root ".tests/data" 30 + vim.env.XDG_STATE_HOME = root ".tests/state" 31 + vim.env.XDG_CACHE_HOME = root ".tests/cache" 32 + 33 + vim.opt.runtimepath:append(root()) 34 + vim.opt.packpath:append(root ".tests/site") 35 + vim.o.swapfile = false 36 + vim.o.writebackup = false 37 + vim.notify = vim.print 38 + 39 + -- install go treesitter parse 40 + require("nvim-treesitter.install").ensure_installed_sync "go" 41 + 42 + require("gopher").setup { 43 + log_level = vim.log.levels.OFF, 44 + timeout = 4000, 45 + } 46 + 47 + -- setup mini.test only when running headless nvim 48 + if #vim.api.nvim_list_uis() == 0 then 49 + require("mini.test").setup { 50 + collect = { 51 + find_files = function() 52 + return vim.fn.globpath("spec", "**/*_test.lua", true, true) 53 + end, 54 + }, 55 + } 56 + end 57 + 58 + -- set colorscheme only when running ui 59 + if #vim.api.nvim_list_uis() == 1 then 60 + vim.cmd.colorscheme "tokyonight-night" 61 + vim.o.cursorline = true 62 + vim.o.number = true 63 + end 64 + 65 + -- needed for tests, i dont know the reason why, but on start 66 + -- vim is not able to use treesitter for go by default 67 + vim.api.nvim_create_autocmd("FileType", { 68 + pattern = "go", 69 + callback = function(args) 70 + vim.treesitter.start(args.buf, "go") 71 + end, 72 + })
+2 -1
selene.toml
··· 1 - std="nvim+lua51" 1 + std = "nvim+lua52" 2 + exclude = [".tests/*"]
spec/fixtures/comment/empty_input.go

This is a binary file and will not be displayed.

+2
spec/fixtures/comment/empty_output.go
··· 1 + // 2 +
+5
spec/fixtures/comment/func_input.go
··· 1 + package main 2 + 3 + func Test(a int) bool { 4 + return false 5 + }
+6
spec/fixtures/comment/func_output.go
··· 1 + package main 2 + 3 + // Test 4 + func Test(a int) bool { 5 + return false 6 + }
+3
spec/fixtures/comment/interface_input.go
··· 1 + package main 2 + 3 + type Testinger interface{}
+6
spec/fixtures/comment/interface_many_method_input.go
··· 1 + package main 2 + 3 + type Testinger interface { 4 + Get(id string) int 5 + Set(id string, val int) 6 + }
+7
spec/fixtures/comment/interface_many_method_output.go
··· 1 + package main 2 + 3 + type Testinger interface { 4 + Get(id string) int 5 + // Set 6 + Set(id string, val int) 7 + }
+5
spec/fixtures/comment/interface_method_input.go
··· 1 + package main 2 + 3 + type Testinger interface { 4 + Method(input string) error 5 + }
+6
spec/fixtures/comment/interface_method_output.go
··· 1 + package main 2 + 3 + type Testinger interface { 4 + // Method 5 + Method(input string) error 6 + }
+4
spec/fixtures/comment/interface_output.go
··· 1 + package main 2 + 3 + // Testinger 4 + type Testinger interface{}
+18
spec/fixtures/comment/many_structs_fields_input.go
··· 1 + package main 2 + 3 + type ( 4 + TestOne struct { 5 + Asdf string 6 + ID int 7 + } 8 + 9 + TestTwo struct { 10 + Fesa int 11 + A bool 12 + } 13 + 14 + TestThree struct { 15 + Asufj int 16 + Fs string 17 + } 18 + )
+19
spec/fixtures/comment/many_structs_fields_output.go
··· 1 + package main 2 + 3 + type ( 4 + TestOne struct { 5 + Asdf string 6 + ID int 7 + } 8 + 9 + TestTwo struct { 10 + // Fesa 11 + Fesa int 12 + A bool 13 + } 14 + 15 + TestThree struct { 16 + Asufj int 17 + Fs string 18 + } 19 + )
+7
spec/fixtures/comment/method_input.go
··· 1 + package main 2 + 3 + type Method struct{} 4 + 5 + func (Method) Run() error { 6 + return nil 7 + }
+8
spec/fixtures/comment/method_output.go
··· 1 + package main 2 + 3 + type Method struct{} 4 + 5 + // Run 6 + func (Method) Run() error { 7 + return nil 8 + }
+1
spec/fixtures/comment/package_input.go
··· 1 + package main
+2
spec/fixtures/comment/package_output.go
··· 1 + // Package main provides 2 + package main
+7
spec/fixtures/comment/struct_fields_input.go
··· 1 + package main 2 + 3 + type CommentStruct struct { 4 + Name string 5 + Address string 6 + Aliases []string 7 + }
+8
spec/fixtures/comment/struct_fields_output.go
··· 1 + package main 2 + 3 + type CommentStruct struct { 4 + Name string 5 + // Address 6 + Address string 7 + Aliases []string 8 + }
+3
spec/fixtures/comment/struct_input.go
··· 1 + package main 2 + 3 + type CommentStruct struct{}
+4
spec/fixtures/comment/struct_output.go
··· 1 + package main 2 + 3 + // CommentStruct 4 + type CommentStruct struct{}
+5
spec/fixtures/comment/svar_input.go
··· 1 + package main 2 + 3 + func varTest() { 4 + s := "something" 5 + }
+6
spec/fixtures/comment/svar_output.go
··· 1 + package main 2 + 3 + func varTest() { 4 + // s 5 + s := "something" 6 + }
+5
spec/fixtures/comment/var_input.go
··· 1 + package main 2 + 3 + func test() { 4 + var imAVar string 5 + }
+6
spec/fixtures/comment/var_output.go
··· 1 + package main 2 + 3 + func test() { 4 + // imAVar 5 + var imAVar string 6 + }
+8
spec/fixtures/comment/var_struct_fields_input.go
··· 1 + package main 2 + 3 + func main() { 4 + var s struct { 5 + API string 6 + Key string 7 + } 8 + }
+9
spec/fixtures/comment/var_struct_fields_output.go
··· 1 + package main 2 + 3 + func main() { 4 + var s struct { 5 + API string 6 + // Key 7 + Key string 8 + } 9 + }
+9
spec/fixtures/iferr/iferr_input.go
··· 1 + package main 2 + 3 + func test() error { 4 + return nil 5 + } 6 + 7 + func main() { 8 + err := test() 9 + }
+12
spec/fixtures/iferr/iferr_output.go
··· 1 + package main 2 + 3 + func test() error { 4 + return nil 5 + } 6 + 7 + func main() { 8 + err := test() 9 + if err != nil { 10 + return 11 + } 12 + }
+7
spec/fixtures/iferr/message_input.go
··· 1 + package main 2 + 3 + func getErr() error { return nil } 4 + 5 + func test() error { 6 + err := getErr() 7 + }
+10
spec/fixtures/iferr/message_output.go
··· 1 + package main 2 + 3 + func getErr() error { return nil } 4 + 5 + func test() error { 6 + err := getErr() 7 + if err != nil { 8 + return fmt.Errorf("failed to %w", err) 9 + } 10 + }
+3
spec/fixtures/impl/closer_input.go
··· 1 + package main 2 + 3 + type CloserTest struct{}
+7
spec/fixtures/impl/closer_output.go
··· 1 + package main 2 + 3 + type CloserTest2 struct{} 4 + 5 + func (closertest *CloserTest2) Close() error { 6 + panic("not implemented") // TODO: Implement 7 + }
+3
spec/fixtures/impl/reader_input.go
··· 1 + package main 2 + 3 + type Read struct{}
+7
spec/fixtures/impl/reader_output.go
··· 1 + package main 2 + 3 + func (r *Read2) Read(p []byte) (n int, err error) { 4 + panic("not implemented") // TODO: Implement 5 + } 6 + 7 + type Read2 struct{}
+3
spec/fixtures/impl/writer_input.go
··· 1 + package main 2 + 3 + type WriterTest struct{}
+7
spec/fixtures/impl/writer_output.go
··· 1 + package main 2 + 3 + type WriterTest2 struct{} 4 + 5 + func (w *WriterTest2) Write(p []byte) (n int, err error) { 6 + panic("not implemented") // TODO: Implement 7 + }
+2
spec/fixtures/json2go/interativly_input.go
··· 1 + package main 2 +
+5
spec/fixtures/json2go/interativly_output.go
··· 1 + package main 2 + 3 + type AutoGenerated struct { 4 + Json bool `json:"json"` 5 + }
+2
spec/fixtures/json2go/manual_input.go
··· 1 + package main 2 +
+7
spec/fixtures/json2go/manual_output.go
··· 1 + package main 2 + 3 + type AutoGenerated struct { 4 + User struct { 5 + Name string `json:"name"` 6 + } `json:"user"` 7 + }
+11
spec/fixtures/tags/add_many_input.go
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int 5 + Name string 6 + Num int64 7 + Another struct { 8 + First int 9 + Second string 10 + } 11 + }
+11
spec/fixtures/tags/add_many_output.go
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int `test4:"id" test5:"id" test1:"id" test2:"id"` 5 + Name string `test4:"name" test5:"name" test1:"name" test2:"name"` 6 + Num int64 `test4:"num" test5:"num" test1:"num" test2:"num"` 7 + Another struct { 8 + First int `test4:"first" test5:"first" test1:"first" test2:"first"` 9 + Second string `test4:"second" test5:"second" test1:"second" test2:"second"` 10 + } `test4:"another" test5:"another" test1:"another" test2:"another"` 11 + }
+14
spec/fixtures/tags/add_range_input.go
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int 5 + Name string 6 + Num int64 7 + Cost int 8 + Thingy []string 9 + Testing int 10 + Another struct { 11 + First int 12 + Second string 13 + } 14 + }
+14
spec/fixtures/tags/add_range_output.go
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int 5 + Name string `gopher:"name"` 6 + Num int64 `gopher:"num"` 7 + Cost int `gopher:"cost"` 8 + Thingy []string 9 + Testing int 10 + Another struct { 11 + First int 12 + Second string 13 + } 14 + }
+11
spec/fixtures/tags/clear_input.go
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int `json:"id" yaml:"id" xml:"id" db:"id"` 5 + Name string `json:"name" yaml:"name" xml:"name" db:"name"` 6 + Num int64 `json:"num" yaml:"num" xml:"num" db:"num"` 7 + Another struct { 8 + First int `json:"first" yaml:"first" xml:"first" db:"first"` 9 + Second string `json:"second" yaml:"second" xml:"second" db:"second"` 10 + } `json:"another" yaml:"another" xml:"another" db:"another"` 11 + }
+11
spec/fixtures/tags/clear_output.go
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int 5 + Name string 6 + Num int64 7 + Another struct { 8 + First int 9 + Second string 10 + } 11 + }
+18
spec/fixtures/tags/many_input.go
··· 1 + package main 2 + 3 + type ( 4 + TestOne struct { 5 + Asdf string 6 + ID int 7 + } 8 + 9 + TestTwo struct { 10 + Fesa int 11 + A bool 12 + } 13 + 14 + TestThree struct { 15 + Asufj int 16 + Fs string 17 + } 18 + )
+18
spec/fixtures/tags/many_output.go
··· 1 + package main 2 + 3 + type ( 4 + TestOne struct { 5 + Asdf string 6 + ID int 7 + } 8 + 9 + TestTwo struct { 10 + Fesa int `testing:"fesa"` 11 + A bool `testing:"a"` 12 + } 13 + 14 + TestThree struct { 15 + Asufj int 16 + Fs string 17 + } 18 + )
+8
spec/fixtures/tags/overwrite_default_option_input.go
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int 5 + Another struct { 6 + Second string 7 + } 8 + }
+8
spec/fixtures/tags/overwrite_default_option_output.go
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int `xml:"id,otheroption"` 5 + Another struct { 6 + Second string `xml:"second,otheroption"` 7 + } `xml:"another,otheroption"` 8 + }
+14
spec/fixtures/tags/remove_range_input.go
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int `asdf:"id"` 5 + Name string `asdf:"name"` 6 + Num int64 `asdf:"num"` 7 + Cost int `asdf:"cost"` 8 + Thingy []string `asdf:"thingy"` 9 + Testing int `asdf:"testing"` 10 + Another struct { 11 + First int `asdf:"first"` 12 + Second string `asdf:"second"` 13 + } `asdf:"another"` 14 + }
+14
spec/fixtures/tags/remove_range_output.go
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int `asdf:"id"` 5 + Name string `asdf:"name"` 6 + Num int64 7 + Cost int 8 + Thingy []string 9 + Testing int `asdf:"testing"` 10 + Another struct { 11 + First int `asdf:"first"` 12 + Second string `asdf:"second"` 13 + } `asdf:"another"` 14 + }
+11
spec/fixtures/tags/remove_with_option_input.go
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int `json:"id,omitempty" xml:"id,someoption"` 5 + Name string `json:"name,omitempty" xml:"name,someoption"` 6 + Num int64 `json:"num,omitempty" xml:"num,someoption"` 7 + Another struct { 8 + First int `json:"first,omitempty" xml:"first,someoption"` 9 + Second string `json:"second,omitempty" xml:"second,someoption"` 10 + } `json:"another,omitempty" xml:"another,someoption"` 11 + }
+11
spec/fixtures/tags/remove_with_option_output.go
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int `xml:"id,someoption"` 5 + Name string `xml:"name,someoption"` 6 + Num int64 `xml:"num,someoption"` 7 + Another struct { 8 + First int `xml:"first,someoption"` 9 + Second string `xml:"second,someoption"` 10 + } `xml:"another,someoption"` 11 + }
+11
spec/fixtures/tags/svar_input.go
··· 1 + package main 2 + 3 + func main() { 4 + s := struct { 5 + API string 6 + Key string 7 + }{ 8 + API: "api.com", 9 + Key: "key", 10 + } 11 + }
+11
spec/fixtures/tags/svar_output.go
··· 1 + package main 2 + 3 + func main() { 4 + s := struct { 5 + API string `xml:"api"` 6 + Key string `xml:"key"` 7 + }{ 8 + API: "api.com", 9 + Key: "key", 10 + } 11 + }
+8
spec/fixtures/tags/var_input.go
··· 1 + package main 2 + 3 + func main() { 4 + var a struct { 5 + TestField1 string 6 + TestField2 string 7 + } 8 + }
+8
spec/fixtures/tags/var_output.go
··· 1 + package main 2 + 3 + func main() { 4 + var a struct { 5 + TestField1 string `yaml:"test_field_1"` 6 + TestField2 string `yaml:"test_field_2"` 7 + } 8 + }
+8
spec/fixtures/tags/with_default_option_input.go
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int 5 + Another struct { 6 + Second string 7 + } 8 + }
+8
spec/fixtures/tags/with_default_option_output.go
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int `xml:"id,theoption"` 5 + Another struct { 6 + Second string `xml:"second,theoption"` 7 + } `xml:"another,theoption"` 8 + }
+11
spec/fixtures/tags/with_option_input.go
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int 5 + Name string 6 + Num int64 7 + Another struct { 8 + First int 9 + Second string 10 + } 11 + }
+11
spec/fixtures/tags/with_option_output.go
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int `json:"id,omitempty"` 5 + Name string `json:"name,omitempty"` 6 + Num int64 `json:"num,omitempty"` 7 + Another struct { 8 + First int `json:"first,omitempty"` 9 + Second string `json:"second,omitempty"` 10 + } `json:"another,omitempty"` 11 + }
+5
spec/fixtures/tests/function_input.go
··· 1 + package fortest 2 + 3 + func Add(x, y int) int { 4 + return 2 + x + y 5 + }
+24
spec/fixtures/tests/function_output.go
··· 1 + package fortest 2 + 3 + import "testing" 4 + 5 + func TestAdd(t *testing.T) { 6 + type args struct { 7 + x int 8 + y int 9 + } 10 + tests := []struct { 11 + name string 12 + args args 13 + want int 14 + }{ 15 + // TODO: Add test cases. 16 + } 17 + for _, tt := range tests { 18 + t.Run(tt.name, func(t *testing.T) { 19 + if got := Add(tt.args.x, tt.args.y); got != tt.want { 20 + t.Errorf("Add() = %v, want %v", got, tt.want) 21 + } 22 + }) 23 + } 24 + }
+7
spec/fixtures/tests/method_input.go
··· 1 + package fortest 2 + 3 + type ForTest struct{} 4 + 5 + func (t *ForTest) Add(x, y int) int { 6 + return 2 + x + y 7 + }
+26
spec/fixtures/tests/method_output.go
··· 1 + package fortest 2 + 3 + import "testing" 4 + 5 + func TestForTest_Add(t *testing.T) { 6 + type args struct { 7 + x int 8 + y int 9 + } 10 + tests := []struct { 11 + name string 12 + tr *ForTest 13 + args args 14 + want int 15 + }{ 16 + // TODO: Add test cases. 17 + } 18 + for _, tt := range tests { 19 + t.Run(tt.name, func(t *testing.T) { 20 + tr := &ForTest{} 21 + if got := tr.Add(tt.args.x, tt.args.y); got != tt.want { 22 + t.Errorf("ForTest.Add() = %v, want %v", got, tt.want) 23 + } 24 + }) 25 + } 26 + }
-41
spec/gopher_config_spec.lua
··· 1 - describe("gopher.config", function() 2 - it("can be required", function() 3 - require "gopher.config" 4 - end) 5 - 6 - it(".setup() when gets empty table not edit config", function() 7 - local c = require "gopher.config" 8 - c.setup {} 9 - 10 - assert.are.same(c.config.commands.go, "go") 11 - assert.are.same(c.config.commands.gomodifytags, "gomodifytags") 12 - assert.are.same(c.config.commands.gotests, "gotests") 13 - assert.are.same(c.config.commands.impl, "impl") 14 - end) 15 - 16 - it(".setup() when get one custom value sets that", function() 17 - local c = require "gopher.config" 18 - c.setup { commands = { 19 - go = "custom_go", 20 - } } 21 - 22 - assert.are.same(c.config.commands.go, "custom_go") 23 - end) 24 - 25 - it(".setup() when get all custom values sets it", function() 26 - local c = require "gopher.config" 27 - c.setup { 28 - commands = { 29 - go = "go1.18", 30 - gomodifytags = "user-gomodifytags", 31 - gotests = "gotests", 32 - impl = "goimpl", 33 - }, 34 - } 35 - 36 - assert.are.same(c.config.commands.go, "go1.18") 37 - assert.are.same(c.config.commands.gomodifytags, "user-gomodifytags") 38 - assert.are.same(c.config.commands.gotests, "gotests") 39 - assert.are.same(c.config.commands.impl, "goimpl") 40 - end) 41 - end)
-5
spec/gopher_spec.lua
··· 1 - describe("gopher", function() 2 - it("can be required", function() 3 - require "gopher" 4 - end) 5 - end)
-56
spec/gopher_struct_tags_spec.lua
··· 1 - local cur_dir = vim.fn.expand "%:p:h" 2 - 3 - describe("gopher.struct_tags", function() 4 - it("can be required", function() 5 - require "gopher.struct_tags" 6 - end) 7 - 8 - it("can add json tag to struct", function() 9 - local add = require("gopher.struct_tags").add 10 - local name = vim.fn.tempname() .. ".go" 11 - local input_file = vim.fn.readfile(cur_dir .. "/spec/fixtures/tags/add_input.go") 12 - local output_file = vim.fn.join( 13 - vim.fn.readfile(cur_dir .. "/spec/fixtures/tags/add_output.go"), 14 - "\n" 15 - ) 16 - 17 - vim.fn.writefile(input_file, name) 18 - vim.cmd("silent exe 'e " .. name .. "'") 19 - 20 - local bufn = vim.fn.bufnr "" 21 - vim.bo.filetype = "go" 22 - vim.fn.setpos(".", { bufn, 3, 6, 0 }) 23 - add() 24 - 25 - vim.wait(100, function() end) 26 - local fmt = vim.fn.join(vim.fn.readfile(name), "\n") 27 - assert.are.same(output_file, fmt) 28 - 29 - vim.cmd("bd! " .. name) 30 - end) 31 - 32 - it("can remove json tag from struct", function() 33 - local remove = require("gopher.struct_tags").remove 34 - local name = vim.fn.tempname() .. ".go" 35 - local input_file = vim.fn.readfile(cur_dir .. "/spec/fixtures/tags/remove_input.go") 36 - local output_file = vim.fn.join( 37 - vim.fn.readfile(cur_dir .. "/spec/fixtures/tags/remove_output.go"), 38 - "\n" 39 - ) 40 - 41 - vim.fn.writefile(input_file, name) 42 - vim.cmd("silent exe 'e " .. name .. "'") 43 - 44 - local bufn = vim.fn.bufnr "" 45 - vim.bo.filetype = "go" 46 - vim.fn.setpos(".", { bufn, 3, 6, 0 }) 47 - remove() 48 - 49 - vim.wait(100, function() end) 50 - 51 - local fmt = vim.fn.join(vim.fn.readfile(name), "\n") 52 - assert.are.same(output_file, fmt) 53 - 54 - vim.cmd("bd! " .. name) 55 - end) 56 - end)
-19
spec/gopher_utils_spec.lua
··· 1 - describe("gopher._utils", function() 2 - it("can be requried", function() 3 - require "gopher._utils" 4 - end) 5 - 6 - it(".empty() with non-empty talbe", function() 7 - local empty = require("gopher._utils").empty 8 - local res = empty { first = "1", second = 2 } 9 - 10 - assert.are.same(res, false) 11 - end) 12 - 13 - it(".empty() with empty talbe", function() 14 - local empty = require("gopher._utils").empty 15 - local res = empty {} 16 - 17 - assert.are.same(res, true) 18 - end) 19 - end)
+65
spec/integration/comment_test.lua
··· 1 + local t = require "spec.testutils" 2 + local child, T, comment = t.setup "comment" 3 + 4 + local function do_the_test(fixture, pos) 5 + local rs = t.setup_test("comment/" .. fixture, child, pos) 6 + child.cmd "GoCmt" 7 + child.cmd "write" 8 + 9 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 10 + t.cleanup(rs) 11 + end 12 + 13 + comment["should add comment to package"] = function() 14 + do_the_test("package", { 1, 1 }) 15 + end 16 + 17 + comment["should add comment to struct"] = function() 18 + do_the_test("struct", { 4, 1 }) 19 + end 20 + 21 + comment["should add a comment on struct field"] = function() 22 + do_the_test("struct_fields", { 5, 8 }) 23 + end 24 + 25 + comment["should add a comment on var struct field"] = function() 26 + do_the_test("var_struct_fields", { 6, 4 }) 27 + end 28 + 29 + comment["should add a comment on one field of many structs"] = function() 30 + do_the_test("many_structs_fields", { 10, 4 }) 31 + end 32 + 33 + comment["should add comment to function"] = function() 34 + do_the_test("func", { 3, 1 }) 35 + end 36 + 37 + comment["should add comment to method"] = function() 38 + do_the_test("method", { 5, 1 }) 39 + end 40 + 41 + comment["should add comment to interface"] = function() 42 + do_the_test("interface", { 3, 6 }) 43 + end 44 + 45 + comment["should add comment on interface method"] = function() 46 + do_the_test("interface_method", { 4, 2 }) 47 + end 48 + 49 + comment["should add a comment on interface with many method"] = function() 50 + do_the_test("interface_many_method", { 5, 2 }) 51 + end 52 + 53 + comment["should add a comment on a var"] = function() 54 + do_the_test("var", { 4, 2 }) 55 + end 56 + 57 + comment["should add a comment on a short declared var"] = function() 58 + do_the_test("svar", { 4, 8 }) 59 + end 60 + 61 + comment["otherwise should add // above cursor"] = function() 62 + do_the_test("empty", { 1, 1 }) 63 + end 64 + 65 + return T
+29
spec/integration/gotests_test.lua
··· 1 + local t = require "spec.testutils" 2 + local child, T, gotests = t.setup "gotests" 3 + 4 + --- NOTE: :GoTestAdd is the only place that has actual logic 5 + --- All other parts are handled `gotests` itself. 6 + 7 + ---@param fpath string 8 + ---@return string 9 + local function read_testfile(fpath) 10 + return t.readfile(fpath:gsub(".go", "_test.go")) 11 + end 12 + 13 + gotests["should add test for function under cursor"] = function() 14 + local rs = t.setup_test("tests/function", child, { 3, 5 }) 15 + child.cmd "GoTestAdd" 16 + 17 + t.eq(rs.fixtures.output, read_testfile(rs.tmp)) 18 + t.cleanup(rs) 19 + end 20 + 21 + gotests["should add test for method under cursor"] = function() 22 + local rs = t.setup_test("tests/method", child, { 5, 19 }) 23 + child.cmd "GoTestAdd" 24 + 25 + t.eq(rs.fixtures.output, read_testfile(rs.tmp)) 26 + t.cleanup(rs) 27 + end 28 + 29 + return T
+28
spec/integration/iferr_test.lua
··· 1 + local t = require "spec.testutils" 2 + local child, T, iferr = t.setup "iferr" 3 + 4 + iferr["should add if != nil {"] = function() 5 + local rs = t.setup_test("iferr/iferr", child, { 8, 2 }) 6 + child.cmd "GoIfErr" 7 + child.cmd "write" 8 + 9 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 10 + t.cleanup(rs) 11 + end 12 + 13 + iferr["should add if err with custom message"] = function() 14 + child.lua [[ 15 + require("gopher").setup { 16 + iferr = { message = 'fmt.Errorf("failed to %w", err)' } 17 + } 18 + ]] 19 + 20 + local rs = t.setup_test("iferr/message", child, { 6, 2 }) 21 + child.cmd "GoIfErr" 22 + child.cmd "write" 23 + 24 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 25 + t.cleanup(rs) 26 + end 27 + 28 + return T
+35
spec/integration/impl_test.lua
··· 1 + local t = require "spec.testutils" 2 + local child, T, impl = t.setup "impl" 3 + 4 + impl["should do impl with 'w io.Writer'"] = function() 5 + local rs = t.setup_test("impl/writer", child, { 3, 0 }) 6 + child.cmd "GoImpl w io.Writer" 7 + child.cmd "write" 8 + 9 + -- NOTE: since "impl" won't implement interface if it's already implemented i went with this hack 10 + local rhs = rs.fixtures.output:gsub("Test2", "Test") 11 + t.eq(t.readfile(rs.tmp), rhs) 12 + t.cleanup(rs) 13 + end 14 + 15 + impl["should work with full input, 'r Read io.Reader'"] = function() 16 + local rs = t.setup_test("impl/reader", child) 17 + child.cmd "GoImpl r Read io.Reader" 18 + child.cmd "write" 19 + 20 + local rhs = rs.fixtures.output:gsub("Read2", "Read") 21 + t.eq(t.readfile(rs.tmp), rhs) 22 + t.cleanup(rs) 23 + end 24 + 25 + impl["should work with minimal input 'io.Closer'"] = function() 26 + local rs = t.setup_test("impl/closer", child, { 3, 6 }) 27 + child.cmd "GoImpl io.Closer" 28 + child.cmd "write" 29 + 30 + local rhs = rs.fixtures.output:gsub("Test2", "Test") 31 + t.eq(t.readfile(rs.tmp), rhs) 32 + t.cleanup(rs) 33 + end 34 + 35 + return T
+25
spec/integration/json2go_test.lua
··· 1 + local t = require "spec.testutils" 2 + local child, T, json2go = t.setup "json2go" 3 + 4 + json2go["should convert interativly"] = function() 5 + local rs = t.setup_test("json2go/interativly", child, { 2, 0 }) 6 + child.cmd "GoJson" 7 + child.type_keys [[{"json": true}]] 8 + child.type_keys "<Esc>" 9 + child.cmd "wq" -- quit prompt 10 + child.cmd "write" -- the fixture file 11 + 12 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 13 + t.cleanup(rs) 14 + end 15 + 16 + json2go["should convert argument"] = function() 17 + local rs = t.setup_test("json2go/manual", child, { 2, 0 }) 18 + child.cmd [[GoJson {"user": {"name": "user-ovic"}}]] 19 + child.cmd "write" 20 + 21 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 22 + t.cleanup(rs) 23 + end 24 + 25 + return T
+147
spec/integration/struct_tags_test.lua
··· 1 + local t = require "spec.testutils" 2 + local child, T, struct_tags = t.setup "struct_tags" 3 + 4 + struct_tags["should add tag"] = function() 5 + local rs = t.setup_test("tags/add", child, { 3, 6 }) 6 + child.cmd "GoTagAdd json" 7 + child.cmd "write" 8 + 9 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 10 + t.cleanup(rs) 11 + end 12 + 13 + struct_tags["should remove tag"] = function() 14 + local rs = t.setup_test("tags/remove", child, { 4, 6 }) 15 + child.cmd "GoTagRm json" 16 + child.cmd "write" 17 + 18 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 19 + t.cleanup(rs) 20 + end 21 + 22 + struct_tags["should be able to handle many structs"] = function() 23 + local rs = t.setup_test("tags/many", child, { 10, 3 }) 24 + child.cmd "GoTagAdd testing" 25 + child.cmd "write" 26 + 27 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 28 + t.cleanup(rs) 29 + end 30 + 31 + struct_tags["should clear struct"] = function() 32 + local rs = t.setup_test("tags/clear", child, { 3, 1 }) 33 + child.cmd "GoTagClear" 34 + child.cmd "write" 35 + 36 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 37 + t.cleanup(rs) 38 + end 39 + 40 + struct_tags["should add more than one tag"] = function() 41 + local tmp = t.tmpfile() 42 + local fixtures = t.get_fixtures "tags/add_many" 43 + t.writefile(tmp, fixtures.input) 44 + 45 + --- with comma, like gomodifytags 46 + child.cmd("silent edit " .. tmp) 47 + child.fn.setpos(".", { child.fn.bufnr(tmp), 3, 1 }) 48 + child.cmd "GoTagAdd test4,test5" 49 + child.cmd "write" 50 + 51 + -- without comma 52 + child.cmd("silent edit " .. tmp) 53 + child.fn.setpos(".", { child.fn.bufnr(tmp), 3, 1 }) 54 + child.cmd "GoTagAdd test1 test2" 55 + child.cmd "write" 56 + 57 + t.eq(t.readfile(tmp), fixtures.output) 58 + 59 + ---@diagnostic disable-next-line:missing-fields 60 + t.cleanup { tmp = tmp } 61 + end 62 + 63 + struct_tags["should add tags on var"] = function() 64 + local rs = t.setup_test("tags/var", child, { 5, 6 }) 65 + child.cmd "GoTagAdd yaml" 66 + child.cmd "write" 67 + 68 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 69 + t.cleanup(rs) 70 + end 71 + 72 + struct_tags["should add tags on short declr var"] = function() 73 + local rs = t.setup_test("tags/svar", child, { 4, 3 }) 74 + child.cmd "GoTagAdd xml" 75 + child.cmd "write" 76 + 77 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 78 + t.cleanup(rs) 79 + end 80 + 81 + struct_tags["should add tag with range"] = function() 82 + local rs = t.setup_test("tags/add_range", child, { 5, 1 }) 83 + child.cmd ".,+2GoTagAdd gopher" 84 + child.cmd "write" 85 + 86 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 87 + t.cleanup(rs) 88 + end 89 + 90 + struct_tags["should remove tag with range"] = function() 91 + local rs = t.setup_test("tags/remove_range", child, { 6, 1 }) 92 + child.cmd ".,+2GoTagRm asdf" 93 + child.cmd "write" 94 + 95 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 96 + t.cleanup(rs) 97 + end 98 + 99 + struct_tags["should add tags with option"] = function() 100 + local rs = t.setup_test("tags/with_option", child, { 3, 6 }) 101 + child.cmd "GoTagAdd json=omitempty" 102 + child.cmd "write" 103 + 104 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 105 + t.cleanup(rs) 106 + end 107 + 108 + struct_tags["should add tags with default option"] = function() 109 + child.lua [[ 110 + require("gopher").setup { 111 + gotag = { option = "xml=theoption" }, 112 + } 113 + ]] 114 + 115 + local rs = t.setup_test("tags/with_default_option", child, { 3, 6 }) 116 + child.cmd "GoTagAdd xml" 117 + child.cmd "write" 118 + 119 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 120 + t.cleanup(rs) 121 + end 122 + 123 + struct_tags["should add tags and overwrite default option"] = function() 124 + child.lua [[ 125 + require("gopher").setup { 126 + gotag = { option = "xml=theoption" }, 127 + } 128 + ]] 129 + 130 + local rs = t.setup_test("tags/overwrite_default_option", child, { 3, 6 }) 131 + child.cmd "GoTagAdd xml=otheroption" 132 + child.cmd "write" 133 + 134 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 135 + t.cleanup(rs) 136 + end 137 + 138 + struct_tags["should remove tag with specified option"] = function() 139 + local rs = t.setup_test("tags/remove_with_option", child, { 3, 6 }) 140 + child.cmd "GoTagRm json=omitempty" 141 + child.cmd "write" 142 + 143 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 144 + t.cleanup(rs) 145 + end 146 + 147 + return T
-3
spec/minimal.vim
··· 1 - set rtp+=. 2 - packadd plenary.nvim 3 - packadd nvim-treesitter
+108
spec/testutils.lua
··· 1 + local base_dir = vim.env.GOPHER_DIR or vim.fn.expand "%:p:h" 2 + 3 + ---@class gopher.TestUtils 4 + local testutils = {} 5 + 6 + testutils.mininit_path = vim.fs.joinpath(base_dir, "scripts", "minimal_init.lua") 7 + testutils.fixtures_dir = vim.fs.joinpath(base_dir, "spec/fixtures") 8 + 9 + ---@param mod string Module name for which to create a nested test set. 10 + ---@return MiniTest.child child nvim client. 11 + ---@return table T root test set created by `MiniTest.new_set()`. 12 + ---@return table mod_name nested set of tests in `T[mod]`. 13 + function testutils.setup(mod) 14 + local child = MiniTest.new_child_neovim() 15 + local T = MiniTest.new_set { 16 + hooks = { 17 + post_once = child.stop, 18 + pre_case = function() 19 + child.restart { "-u", testutils.mininit_path } 20 + end, 21 + }, 22 + } 23 + 24 + T[mod] = MiniTest.new_set {} 25 + return child, T, T[mod] 26 + end 27 + 28 + ---@generic T 29 + ---@param a T 30 + ---@param b T 31 + ---@return boolean 32 + function testutils.eq(a, b) 33 + return MiniTest.expect.equality(a, b) 34 + end 35 + 36 + ---@return string 37 + function testutils.tmpfile() 38 + return vim.fn.tempname() .. ".go" 39 + end 40 + 41 + ---@param path string 42 + ---@return string 43 + function testutils.readfile(path) 44 + return vim.fn.join(vim.fn.readfile(path), "\n") 45 + end 46 + 47 + ---@param fpath string 48 + ---@param contents string 49 + function testutils.writefile(fpath, contents) 50 + vim.fn.writefile(vim.split(contents, "\n"), fpath) 51 + end 52 + 53 + ---@param fpath string 54 + function testutils.deletefile(fpath) 55 + vim.fn.delete(fpath) 56 + end 57 + 58 + ---@class gopher.TestUtilsFixtures 59 + ---@field input string 60 + ---@field output string 61 + 62 + ---@param fixture string 63 + ---@return gopher.TestUtilsFixtures 64 + function testutils.get_fixtures(fixture) 65 + return { 66 + input = testutils.readfile(vim.fs.joinpath(testutils.fixtures_dir, fixture) .. "_input.go"), 67 + output = testutils.readfile(vim.fs.joinpath(testutils.fixtures_dir, fixture) .. "_output.go"), 68 + } 69 + end 70 + 71 + ---@class gopher.TestUtilsSetup 72 + ---@field tmp string 73 + ---@field fixtures gopher.TestUtilsFixtures 74 + ---@field bufnr number 75 + 76 + ---@param fixture string 77 + ---@param child MiniTest.child 78 + ---@param pos? number[] 79 + ---@return gopher.TestUtilsSetup 80 + function testutils.setup_test(fixture, child, pos) 81 + vim.validate("pos", pos, "table", true) 82 + 83 + local tmp = testutils.tmpfile() 84 + local fixtures = testutils.get_fixtures(fixture) 85 + 86 + testutils.writefile(tmp, fixtures.input) 87 + child.cmd("silent edit " .. tmp) 88 + 89 + local bufnr = child.fn.bufnr(tmp) 90 + if pos then 91 + assert(#pos == 2, "invalid cursor position") 92 + 93 + child.fn.setpos(".", { bufnr, unpack(pos) }) 94 + end 95 + 96 + return { 97 + tmp = tmp, 98 + bufnr = bufnr, 99 + fixtures = fixtures, 100 + } 101 + end 102 + 103 + ---@param inp gopher.TestUtilsSetup 104 + function testutils.cleanup(inp) 105 + testutils.deletefile(inp.tmp) 106 + end 107 + 108 + return testutils
+25
spec/unit/config_test.lua
··· 1 + local t = require "spec.testutils" 2 + local _, T, config = t.setup "config" 3 + 4 + config["can be called without any arguments passed"] = function() 5 + ---@diagnostic disable-next-line: missing-parameter 6 + require("gopher").setup() 7 + end 8 + 9 + config["can be called with empty table"] = function() 10 + ---@diagnostic disable-next-line: missing-fields 11 + require("gopher").setup {} 12 + end 13 + 14 + config["should change option"] = function() 15 + local log_level = 1234567890 16 + 17 + ---@diagnostic disable-next-line: missing-fields 18 + require("gopher").setup { 19 + log_level = log_level, 20 + } 21 + 22 + t.eq(log_level, require("gopher.config").log_level) 23 + end 24 + 25 + return T
+68
spec/unit/struct_tag_test.lua
··· 1 + local t = require "spec.testutils" 2 + local _, T, st = t.setup "struct_tags" 3 + 4 + st["should parse tags"] = function() 5 + local out = require("gopher.struct_tags").parse_args { "json", "yaml", "etc" } 6 + 7 + t.eq(out.tags, "json,yaml,etc") 8 + t.eq(out.options, "") 9 + end 10 + 11 + st["should parse tags separated by commas"] = function() 12 + local out = require("gopher.struct_tags").parse_args { "json,yaml,etc" } 13 + 14 + t.eq(out.tags, "json,yaml,etc") 15 + t.eq(out.options, "") 16 + end 17 + 18 + st["should parse tags separated by command and spaces"] = function() 19 + local out = require("gopher.struct_tags").parse_args { 20 + "json,yaml", 21 + "json=omitempty", 22 + "xml=something", 23 + } 24 + 25 + t.eq(out.tags, "json,yaml,xml") 26 + t.eq(out.options, "json=omitempty,xml=something") 27 + end 28 + 29 + st["should parse tag with an option"] = function() 30 + local out = require("gopher.struct_tags").parse_args { 31 + "json=omitempty", 32 + "xml", 33 + "xml=theoption", 34 + } 35 + 36 + t.eq(out.tags, "json,xml") 37 + t.eq(out.options, "json=omitempty,xml=theoption") 38 + end 39 + 40 + st["should parse tags with an option"] = function() 41 + local out = require("gopher.struct_tags").parse_args { "json=omitempty", "yaml" } 42 + 43 + t.eq(out.tags, "json,yaml") 44 + t.eq(out.options, "json=omitempty") 45 + end 46 + 47 + st["should parse tags with an option separated with comma"] = function() 48 + local out = require("gopher.struct_tags").parse_args { "json=omitempty,yaml" } 49 + 50 + t.eq(out.tags, "json,yaml") 51 + t.eq(out.options, "json=omitempty") 52 + end 53 + 54 + st["should parse tags with options specified separately"] = function() 55 + local out = require("gopher.struct_tags").parse_args { "json", "yaml", "json=omitempty" } 56 + 57 + t.eq(out.tags, "json,yaml") 58 + t.eq(out.options, "json=omitempty") 59 + end 60 + 61 + st["should parse tags with options specified separately and separated by comma"] = function() 62 + local out = require("gopher.struct_tags").parse_args { "json,yaml,json=omitempty" } 63 + 64 + t.eq(out.tags, "json,yaml") 65 + t.eq(out.options, "json=omitempty") 66 + end 67 + 68 + return T
+59
spec/unit/utils_test.lua
··· 1 + local t = require "spec.testutils" 2 + local _, T, utils = t.setup "utils" 3 + 4 + utils["should .remove_empty_lines()"] = function() 5 + local u = require "gopher._utils" 6 + local inp = { "hi", "", "a", "", "", "asdf" } 7 + 8 + t.eq(u.remove_empty_lines(inp), { "hi", "a", "asdf" }) 9 + end 10 + 11 + utils["should .readfile_joined()"] = function() 12 + local data = "line1\nline2\nline3" 13 + local tmp = t.tmpfile() 14 + local u = require "gopher._utils" 15 + 16 + t.writefile(tmp, data) 17 + t.eq(u.readfile_joined(tmp), data) 18 + end 19 + 20 + utils["should .trimend()"] = function() 21 + local u = require "gopher._utils" 22 + t.eq(u.trimend " hi ", " hi") 23 + end 24 + 25 + utils["should add .indent() spaces"] = function() 26 + local u = require "gopher._utils" 27 + local line = " func Test() error {" 28 + local indent = 4 29 + 30 + t.eq(" ", u.indent(line, indent)) 31 + end 32 + 33 + utils["should add .indent() a tab"] = function() 34 + local u = require "gopher._utils" 35 + local line = "\tfunc Test() error {" 36 + local indent = 1 37 + 38 + t.eq("\t", u.indent(line, indent)) 39 + end 40 + 41 + utils["should add .indent() 2 tabs"] = function() 42 + local u = require "gopher._utils" 43 + local line = "\t\tfunc Test() error {" 44 + local indent = 2 45 + 46 + t.eq("\t\t", u.indent(line, indent)) 47 + end 48 + 49 + utils["should .list_unique on list with duplicates"] = function() 50 + local u = require "gopher._utils" 51 + t.eq({ "json", "xml" }, u.list_unique { "json", "xml", "json" }) 52 + end 53 + 54 + utils["should .list_unique on list with no duplicates"] = function() 55 + local u = require "gopher._utils" 56 + t.eq({ "json", "xml" }, u.list_unique { "json", "xml" }) 57 + end 58 + 59 + return T
+23
vhs/Taskfile.yml
··· 1 + version: "3" 2 + tasks: 3 + generate: 4 + deps: 5 + - comment 6 + - iferr 7 + - tags 8 + - impl 9 + 10 + comment: 11 + cmd: vhs comment.tape 12 + 13 + iferr: 14 + cmd: vhs iferr.tape 15 + 16 + tags: 17 + cmd: vhs tags.tape 18 + 19 + impl: 20 + cmd: vhs impl.tape 21 + 22 + json2go: 23 + cmd: vhs json2go.tape
vhs/comment.gif

This is a binary file and will not be displayed.

+7
vhs/comment.go
··· 1 + package demos 2 + 3 + func doSomethingImportant() {} 4 + 5 + type User struct{} 6 + 7 + type DataProvider interface{}
+29
vhs/comment.tape
··· 1 + Output comment.gif 2 + Require nvim 3 + 4 + Set FontFamily "JetBrains Mono" 5 + Set Height 800 6 + Set Width 1500 7 + Set Padding 20 8 + Set Shell "bash" 9 + Set Theme "tokyonight" 10 + Set TypingSpeed 250ms 11 + 12 + Hide Type@0ms "./nvim.sh comment.go" Enter Show 13 + 14 + # package 15 + Type ":GoCmt" Enter Sleep 500ms Escape Sleep 700ms 16 + 17 + # func 18 + Type@400ms "jjj" 19 + Type ":GoCmt" Enter Sleep 500ms Escape Sleep 700ms 20 + 21 + # struct 22 + Type@400ms "jjj" 23 + Type ":GoCmt" Enter Sleep 500ms Escape Sleep 700ms 24 + 25 + # interface 26 + Type@400ms "jjj" 27 + Type ":GoCmt" Enter Sleep 500ms Escape Sleep 700ms 28 + 29 + Sleep 5s
+3
vhs/go.mod
··· 1 + module demos 2 + 3 + go 1.25.0
vhs/iferr.gif

This is a binary file and will not be displayed.

+11
vhs/iferr.go
··· 1 + package demos 2 + 3 + func ifErr() error { 4 + out, err := doSomething() 5 + 6 + _ = out 7 + } 8 + 9 + func doSomething() (string, error) { 10 + return "", nil 11 + }
+18
vhs/iferr.tape
··· 1 + Output iferr.gif 2 + Require nvim 3 + Require iferr 4 + 5 + Set FontFamily "JetBrains Mono" 6 + Set Height 800 7 + Set Width 1500 8 + Set Padding 20 9 + Set Shell "bash" 10 + Set Theme "tokyonight" 11 + Set TypingSpeed 250ms 12 + 13 + Hide Type@0ms "./nvim.sh iferr.go" Enter Show 14 + 15 + Type "3j" 16 + Type ":GoIfErr" Sleep 500ms Enter 17 + 18 + Sleep 5s
vhs/impl.gif

This is a binary file and will not be displayed.

+3
vhs/impl.go
··· 1 + package demos 2 + 3 + type CloserExample struct{}
+18
vhs/impl.tape
··· 1 + Output impl.gif 2 + Require nvim 3 + Require impl 4 + 5 + Set FontFamily "JetBrains Mono" 6 + Set Height 800 7 + Set Width 1500 8 + Set Padding 20 9 + Set Shell "bash" 10 + Set Theme "tokyonight" 11 + Set TypingSpeed 250ms 12 + 13 + Hide Type@0ms "./nvim.sh impl.go" Enter Show 14 + 15 + Type "2j" 16 + Type ":GoImpl c io.Reader" Sleep 700ms Enter 17 + 18 + Sleep 5s
vhs/json2go.gif

This is a binary file and will not be displayed.

+2
vhs/json2go.go
··· 1 + package main 2 +
+27
vhs/json2go.tape
··· 1 + Output json2go.gif 2 + Require nvim 3 + Require json2go 4 + 5 + Set FontFamily "JetBrains Mono" 6 + Set Height 800 7 + Set Width 1500 8 + Set Padding 20 9 + Set Shell "bash" 10 + Set Theme "tokyonight" 11 + Set TypingSpeed 250ms 12 + 13 + Hide Type@0ms "./nvim.sh json2go.go" Enter Show 14 + 15 + Type@0ms "G" 16 + Type@400ms ":GoJson" Sleep 500ms Enter 17 + Type@70ms "{" Enter 18 + Type@70ms `"json": true,` Enter 19 + Type@70ms `"user": {"name": "Name", "of_age": true}` Enter 20 + Type@70ms "}" 21 + Escape Type@500ms ":wq" Enter 22 + Sleep 1s 23 + 24 + Type@25ms "G2o" Escape 25 + Type@120ms `:GoJson {"json": true}` Enter 26 + 27 + Sleep 5s
+2
vhs/nvim.sh
··· 1 + #!/usr/bin/env bash 2 + nvim --clean -u "../scripts/minimal_init.lua" $@
vhs/tags.gif

This is a binary file and will not be displayed.

+12
vhs/tags.go
··· 1 + package demos 2 + 3 + type AddTagsToMe struct { 4 + Name string 5 + ID int 6 + Address string 7 + Aliases []string 8 + Nested struct { 9 + Foo string 10 + Bar float32 11 + } 12 + }
+31
vhs/tags.tape
··· 1 + Output tags.gif 2 + Require nvim 3 + Require gomodifytags 4 + 5 + Set FontFamily "JetBrains Mono" 6 + Set Height 800 7 + Set Width 1500 8 + Set Padding 20 9 + Set Shell "bash" 10 + Set Theme "tokyonight" 11 + Set TypingSpeed 250ms 12 + 13 + Hide Type@0ms "./nvim.sh tags.go" Enter Show 14 + 15 + Type@400ms "jjj" 16 + Type ":GoTagAdd json yaml" Sleep 500ms Enter 17 + Type@120ms ":w" Enter 18 + Sleep 1s 19 + 20 + Type ":GoTagRm json" Sleep 500ms Enter 21 + Type ":w" Enter 22 + Sleep 1s 23 + 24 + Type ":GoTagClear" Sleep 500ms Enter 25 + Type ":w" Enter 26 + Sleep 1s 27 + 28 + Type@400ms "jVjj" 29 + Type ":GoTagAdd json=omitempty" Sleep 500ms Enter 30 + 31 + Sleep 5s