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

Merge pull request #106 from olexsmir/develop

authored by olexsmir.xyz and committed by GitHub 847c79ab 0ed14a40

+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
+42 -3
.github/workflows/linters.yml
··· 1 1 name: linters 2 - on: [push, pull_request] 2 + 3 + on: 4 + push: 5 + branches: 6 + - main 7 + - develop 8 + pull_request: 3 9 4 10 jobs: 5 11 linters: 6 - name: linters 12 + name: Lua 7 13 runs-on: ubuntu-latest 8 14 steps: 9 - - uses: actions/checkout@v3 15 + - uses: actions/checkout@v4 10 16 - uses: JohnnyMorganz/stylua-action@v3 11 17 with: 12 18 token: ${{ secrets.GITHUB_TOKEN }} ··· 17 23 with: 18 24 token: ${{ secrets.GITHUB_TOKEN }} 19 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 " ")
+38 -23
.github/workflows/tests.yml
··· 1 1 name: tests 2 - on: [push, pull_request] 2 + 3 + on: 4 + push: 5 + branches: 6 + - main 7 + - develop 8 + pull_request: 3 9 4 10 jobs: 5 11 tests: 6 12 strategy: 7 13 matrix: 8 14 os: [ubuntu-latest] 9 - nvim_version: 15 + version: 16 + - stable 10 17 - nightly 11 - - v0.7.0 12 - - v0.7.2 13 - - v0.8.0 14 - - v0.8.1 15 - - v0.8.2 16 - - v0.8.3 17 - - v0.9.0 18 - - v0.9.1 19 - - v0.9.2 20 - - v0.9.4 21 - - v0.9.5 22 - - v0.10.0 23 18 runs-on: ${{ matrix.os }} 24 19 steps: 25 20 - name: Install Task ··· 28 23 version: 3.x 29 24 repo-token: ${{ secrets.GITHUB_TOKEN }} 30 25 31 - - uses: actions/checkout@v3 26 + - name: Install Go 27 + uses: actions/setup-go@v5 28 + with: 29 + go-version: "1.24.0" 30 + check-latest: false 31 + 32 + - name: Install NeoVim 33 + uses: rhysd/action-setup-vim@v1 34 + with: 35 + neovim: true 36 + version: ${{ matrix.version }} 32 37 33 - - name: Install Neovim 38 + - uses: actions/checkout@v4 39 + 40 + - name: Cache .tests 41 + uses: actions/cache@v4 42 + with: 43 + path: | 44 + ${{ github.workspace }}/.tests 45 + ~/.cache/go-build 46 + ~/go/pkg/mod 47 + key: ${{ runner.os }}-tests-${{ hashFiles('${{ github.workspace }}/.tests') }} 48 + 49 + - name: Install Go bins 34 50 run: | 35 - mkdir -p /tmp/nvim 36 - wget -q https://github.com/neovim/neovim/releases/download/${{ matrix.nvim_version }}/nvim.appimage -O /tmp/nvim/nvim.appimage 37 - cd /tmp/nvim 38 - chmod a+x ./nvim.appimage 39 - ./nvim.appimage --appimage-extract 40 - echo "/tmp/nvim/squashfs-root/usr/bin/" >> $GITHUB_PATH 51 + # TODO: install with :GoInstallDeps 52 + go install github.com/fatih/gomodifytags@latest 53 + go install github.com/josharian/impl@latest 54 + go install github.com/cweill/gotests/...@latest 55 + go install github.com/koron/iferr@latest 41 56 42 57 - name: Run Tests 43 58 run: | 44 59 nvim --version 45 - task test 60 + task tests
+1
.gitignore
··· 1 1 /playground/ 2 2 /.tests/ 3 + /.env
-10
.luarc.json
··· 1 - { 2 - "diagnostics.globals": [ 3 - "describe", 4 - "it", 5 - "before_each", 6 - "after_each", 7 - "before_all", 8 - "after_all" 9 - ] 10 - }
+5 -9
CONTRIBUTING.md
··· 18 18 ```bash 19 19 sudo pacman -S selene stylua 20 20 # or whatever is your package manager 21 - # or way of installing pkgs 22 21 ``` 23 22 24 23 For formatting use this following commands, or setup your editor to integrate with selene/stylua: 25 24 ```bash 26 - task format 27 - task format:check # will check if your code formatted 28 - task lint 25 + task stylua 26 + task lint # lintering and format chewing 29 27 ``` 30 28 31 29 ### Documentation ··· 39 37 ``` 40 38 41 39 ### Commit messages 40 + 42 41 We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), please follow it. 43 42 44 43 ### Testing 45 44 46 - For testing this plugins uses [plenary.nvim](https://github.com/nvim-lua/plenary.nvim). 47 - All tests live in [/spec](https://github.com/olexsmir/gopher.nvim/tree/main/spec) dir. 45 + For testing this plugins uses [mini.test](https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-test.md). 46 + All tests live in [/spec](./spec) dir. 48 47 49 48 You can run tests with: 50 49 ```bash 51 - task test 52 - # also there are some aliases for that 53 50 task tests 54 - task spec 55 51 ```
+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.
+19 -27
README.md
··· 10 10 11 11 ## Install (using [lazy.nvim](https://github.com/folke/lazy.nvim)) 12 12 13 - Pre-dependency: 13 + Requirements: 14 14 15 - - [Go](https://github.com/golang/go) 16 - - `go` treesitter parser, install by `:TSInstall go` 15 + - **Neovim 0.10** or later 16 + - Treesitter `go` parser(`:TSInstall go`) 17 + - [Go](https://github.com/golang/go) installed (tested on 1.23) 17 18 18 19 ```lua 19 20 { 20 21 "olexsmir/gopher.nvim", 21 22 ft = "go", 22 - -- branch = "develop", -- if you want develop branch 23 - -- keep in mind, it might break everything 23 + -- branch = "develop" 24 24 dependencies = { 25 - "nvim-lua/plenary.nvim", 26 25 "nvim-treesitter/nvim-treesitter", 27 - "mfussenegger/nvim-dap", -- (optional) only if you use `gopher.dap` 28 26 }, 29 27 -- (optional) will update plugin's deps on every update 30 28 build = function() ··· 35 33 } 36 34 ``` 37 35 38 - ## Configuratoin 36 + ## Configuration 39 37 40 38 > [!IMPORTANT] 41 39 > 42 40 > If you need more info look `:h gopher.nvim` 43 41 44 - **Take a look at default options** 42 + **Take a look at default options (might be a bit outdated, look `:h gopher.nvim-config`)** 45 43 46 44 ```lua 47 45 require("gopher").setup { 46 + -- log level, you might consider using DEBUG or TRACE for debugging the plugin 47 + log_level = vim.log.levels.INFO, 48 + 49 + -- timeout for running internal commands 50 + timeout = 2000, 51 + 48 52 commands = { 49 53 go = "go", 50 54 gomodifytags = "gomodifytags", 51 55 gotests = "gotests", 52 56 impl = "impl", 53 57 iferr = "iferr", 54 - dlv = "dlv", 55 58 }, 56 59 gotests = { 57 60 -- gotests doesn't have template named "default" so this plugin uses "default" to set the default template ··· 59 62 -- path to a directory containing custom test code templates 60 63 template_dir = nil, 61 64 -- switch table tests from using slice to map (with test name for the key) 62 - -- works only with gotests installed from develop branch 63 65 named = false, 64 66 }, 65 67 gotag = { 66 68 transform = "snakecase", 69 + -- default tags to add to struct fields 70 + default_tag = "json", 71 + }, 72 + iferr = { 73 + -- choose a custom error message 74 + message = nil, 67 75 }, 68 76 } 69 77 ``` ··· 87 95 - [impl](https://github.com/josharian/impl) 88 96 - [gotests](https://github.com/cweill/gotests) 89 97 - [iferr](https://github.com/koron/iferr) 90 - - [dlv](github.com/go-delve/delve/cmd/dlv) 91 98 </details> 92 99 93 100 <details> ··· 215 222 ``` 216 223 </details> 217 224 218 - <details> 219 - <summary> 220 - <b>Setup <a href="https://github.com/mfussenegger/nvim-dap">nvim-dap</a> for go in one line</b> 221 - </summary> 222 - 223 - THIS FEATURE WILL BE REMOVED IN `0.1.6` 224 - 225 - note [nvim-dap](https://github.com/mfussenegger/nvim-dap) has to be installed 226 - 227 - ```lua 228 - require("gopher.dap").setup() 229 - ``` 230 - </details> 231 - 232 225 ## Contributing 233 226 234 227 PRs are always welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) ··· 236 229 ## Thanks 237 230 238 231 - [go.nvim](https://github.com/ray-x/go.nvim) 239 - - [nvim-dap-go](https://github.com/leoluz/nvim-dap-go) 240 232 - [iferr](https://github.com/koron/iferr)
+7 -22
Taskfile.yml
··· 1 1 version: "3" 2 2 tasks: 3 - format: 4 - desc: formats all lua files in repo 5 - cmds: 6 - - stylua . 7 - 8 3 lint: 9 4 desc: runs all linters 10 5 cmds: 11 6 - task: selene 12 - - task: stylua:check 7 + - stylua --check . 13 8 14 9 selene: 15 10 desc: runs lua linter(selene) 16 11 cmds: 17 12 - selene . 18 13 19 - stylua:check: 20 - desc: runs stylua in check mode 21 - cmds: 22 - - stylua --check . 23 - 24 14 stylua: 25 15 desc: runs lua formatter 26 16 cmds: 27 17 - stylua . 28 18 29 - test: 30 - desc: runs all tests 31 - aliases: [tests, spec] 19 + tests: 20 + desc: run all tests 32 21 cmds: 33 22 - | 34 - nvim --headless \ 35 - -u ./scripts/minimal_init.lua \ 36 - -c "PlenaryBustedDirectory spec \ 37 - {minimal_init='./scripts/minimal_init.lua' \ 38 - ,sequential=true}" \ 39 - -c ":qa!" 23 + nvim --clean --headless \ 24 + -u ./scripts/minimal_init.lua \ 25 + -c "lua MiniTest.run()" 40 26 41 27 docgen: 42 28 desc: generate vimhelp 43 29 cmds: 44 30 - | 45 - nvim --noplugin \ 46 - --headless \ 31 + nvim --clean --headless \ 47 32 -u "./scripts/minimal_init.lua" \ 48 33 -c "luafile ./scripts/docgen.lua" \ 49 34 -c ":qa!"
-3
autoload/health/gopher.vim
··· 1 - function! health#gopher#check() 2 - lua require("gopher.health").check() 3 - endfunction
+94 -94
doc/gopher.nvim.txt
··· 1 - *gopher.nvim* 1 + *gopher.nvim* Enhance your golang experience 2 + 3 + MIT License Copyright (c) 2025 Oleksandr Smirnov 2 4 3 5 ============================================================================== 4 6 5 7 gopher.nvim is a minimalistic plugin for Go development in Neovim written in Lua. 6 8 It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim. 7 9 8 - ------------------------------------------------------------------------------ 9 - *gopher.nvim-table-of-contents* 10 10 Table of Contents 11 - Setup....................................................|gopher.nvim-setup| 12 - Install dependencies..............................|gopher.nvim-install-deps| 13 - Configuration...........................................|gopher.nvim-config| 14 - Modifty struct tags................................|gopher.nvim-struct-tags| 15 - Auto implementation of interface methods..................|gopher.nvim-impl| 16 - Generating unit tests boilerplate......................|gopher.nvim-gotests| 17 - Iferr....................................................|gopher.nvim-iferr| 18 - Generate comments.....................................|gopher.nvim-comments| 19 - Setup `nvim-dap` for Go......................................|gopher.nvim-dap| 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 + Auto implementation of interface methods ................ |gopher.nvim-impl| 17 + Generating unit tests boilerplate .................... |gopher.nvim-gotests| 18 + Iferr .................................................. |gopher.nvim-iferr| 19 + Generate comments ................................... |gopher.nvim-comments| 20 20 21 21 ------------------------------------------------------------------------------ 22 - *gopher.nvim-setup* 22 + *gopher.nvim-setup()* 23 23 `gopher.setup`({user_config}) 24 - Setup function. This method simply merges default configs with opts table. 24 + Setup function. This method simply merges default config with opts table. 25 25 You can read more about configuration at |gopher.nvim-config| 26 - Calling this function is optional, if you ok with default settings. Look |gopher.nvim.config-defaults| 26 + Calling this function is optional, if you ok with default settings. 27 + See |gopher.nvim.config| 27 28 28 29 Usage ~ 29 - `require("gopher").setup {}` (replace `{}` with your `config` table) 30 + >lua 31 + require("gopher").setup {} -- use default config or replace {} with your own 32 + < 30 33 Parameters ~ 31 - {user_config} gopher.Config 34 + {user_config} `(gopher.Config)` See |gopher.nvim-config| 32 35 33 36 ------------------------------------------------------------------------------ 34 - *gopher.nvim-install-deps* 37 + *gopher.nvim-dependencies* 35 38 `gopher.install_deps` 36 39 Gopher.nvim implements most of its features using third-party tools. 37 - To install these tools, you can run `:GoInstallDeps` command 38 - or call `require("gopher").install_deps()` if you want ues lua api. 40 + To install these tools, you can run `:GoInstallDeps` command 41 + or call `require("gopher").install_deps()` if you want to use lua api. 42 + By default dependencies will be installed asynchronously, 43 + to install them synchronously pass `{sync = true}` as an argument. 39 44 40 45 41 46 ============================================================================== 42 47 ------------------------------------------------------------------------------ 43 48 *gopher.nvim-config* 44 - config it is the place where you can configure the plugin. 45 - also this is optional is you're ok with default settings. 46 - You can look at default options |gopher.nvim-config-defaults| 47 - 48 - ------------------------------------------------------------------------------ 49 - *gopher.nvim-config-defaults* 50 49 `default_config` 51 50 >lua 52 51 local default_config = { 53 - --minidoc_replace_end 54 - 55 - -- log level, you might consider using DEBUG or TRACE for degugging the plugin 52 + -- log level, you might consider using DEBUG or TRACE for debugging the plugin 56 53 ---@type number 57 54 log_level = vim.log.levels.INFO, 55 + 56 + -- timeout for running internal commands 57 + ---@type number 58 + timeout = 2000, 58 59 59 60 -- user specified paths to binaries 60 61 ---@class gopher.ConfigCommand ··· 64 65 gotests = "gotests", 65 66 impl = "impl", 66 67 iferr = "iferr", 67 - dlv = "dlv", 68 68 }, 69 69 ---@class gopher.ConfigGotests 70 70 gotests = { ··· 74 74 ---@type string|nil 75 75 template_dir = nil, 76 76 -- switch table tests from using slice to map (with test name for the key) 77 - -- works only with gotests installed from develop branch 78 77 named = false, 79 78 }, 80 79 ---@class gopher.ConfigGoTag 81 80 gotag = { 82 81 ---@type gopher.ConfigGoTagTransform 83 82 transform = "snakecase", 83 + 84 + -- default tags to add to struct fields 85 + default_tag = "json", 86 + }, 87 + iferr = { 88 + -- choose a custom error message 89 + ---@type string|nil 90 + message = nil, 84 91 }, 85 92 } 86 93 < ··· 90 97 91 98 ============================================================================== 92 99 ------------------------------------------------------------------------------ 100 + *gopher.nvim-commands* 101 + 102 + If don't want to automatically register plugins' commands, 103 + you can set `vim.g.gopher_register_commands` to `false`, before loading the plugin. 104 + 105 + 106 + ============================================================================== 107 + ------------------------------------------------------------------------------ 93 108 *gopher.nvim-struct-tags* 94 - struct-tags is utilizing the `gomodifytags` tool to add or remove tags to struct fields. 109 + 110 + `struct_tags` is utilizing the `gomodifytags` tool to add or remove tags to struct fields. 111 + 95 112 Usage ~ 96 - - put your coursor on the struct 97 - - run `:GoTagAdd json` to add json tags to struct fields 98 - - run `:GoTagRm json` to remove json tags to struct fields 113 + 114 + How to add/remove tags to struct fields: 115 + 1. Place cursor on the struct 116 + 2. Run `:GoTagAdd json` to add json tags to struct fields 117 + 3. Run `:GoTagRm json` to remove json tags to struct fields 118 + 119 + To clear all tags from struct run: `:GoTagClear` 99 120 100 - note: if you dont spesify the tag it will use `json` as default 121 + NOTE: if you dont specify the tag it will use `json` as default 101 122 102 - simple example: 123 + Example: 103 124 >go 104 125 // before 105 126 type User struct { ··· 116 137 } 117 138 < 118 139 119 - 120 140 ============================================================================== 121 141 ------------------------------------------------------------------------------ 122 142 *gopher.nvim-impl* 123 - impl is utilizing the `impl` tool to generate method stubs for interfaces. 143 + 144 + Integration of `impl` tool to generate method stubs for interfaces. 145 + 124 146 Usage ~ 147 + 1. Automatically implement an interface for a struct: 148 + - Place your cursor on the struct where you want to implement the interface. 149 + - Run `:GoImpl io.Reader` 150 + - This will automatically determine the receiver and implement the `io.Reader` interface. 125 151 126 - 1. put your coursor on the struct on which you want implement the interface 127 - and run `:GoImpl io.Reader` 128 - which will automatically choose the reciver for the methods and 129 - implement the `io.Reader` interface 130 - 2. same as previous but with custom receiver, so put your coursor on the struct 131 - run `:GoImpl w io.Writer` 132 - where `w` is the receiver and `io.Writer` is the interface 133 - 3. specift receiver, struct, and interface 134 - there's no need to put your coursor on the struct if you specify all arguments 135 - `:GoImpl r RequestReader io.Reader` 136 - where `r` is the receiver, `RequestReader` is the struct and `io.Reader` is the interface 152 + 2. Specify a custom receiver: 153 + - Place your cursor on the struct 154 + - Run `:GoImpl w io.Writer`, where: 155 + - `w` is the receiver. 156 + - `io.Writer` is the interface to implement. 157 + 158 + 3. Explicitly specify the receiver, struct, and interface: 159 + - No need to place the cursor on the struct if all arguments are provided. 160 + - Run `:GoImpl r RequestReader io.Reader`, where: 161 + - `r` is the receiver. 162 + - `RequestReader` is the struct. 163 + - `io.Reader` is the interface to implement. 137 164 138 - simple example: 165 + Example: 139 166 >go 140 167 type BytesReader struct{} 141 168 // ^ put your cursor here ··· 143 170 144 171 // this is what you will get 145 172 func (b *BytesReader) Read(p []byte) (n int, err error) { 146 - panic("not implemented") // TODO: Implement 173 + panic("not implemented") // TODO: Implement 147 174 } 148 175 < 149 - 150 176 151 177 ============================================================================== 152 178 ------------------------------------------------------------------------------ ··· 154 180 gotests is utilizing the `gotests` tool to generate unit tests boilerplate. 155 181 Usage ~ 156 182 157 - - generate unit test for spesisfic function/method 158 - - to specift the function/method put your cursor on it 159 - - run `:GoTestAdd` 183 + - Generate unit test for specific function/method: 184 + 1. Place your cursor on the desired function/method. 185 + 2. Run `:GoTestAdd` 160 186 161 - - generate unit tests for all functions/methods in current file 187 + - Generate unit tests for *all* functions/methods in current file: 162 188 - run `:GoTestsAll` 163 189 164 - - generate unit tests only for exported(public) functions/methods 190 + - Generate unit tests *only* for *exported(public)* functions/methods: 165 191 - run `:GoTestsExp` 166 192 167 - you can also specify the template to use for generating the tests. see |gopher.nvim-config| 168 - more details about templates can be found at: https://github.com/cweill/gotests 169 - 170 - 171 - ------------------------------------------------------------------------------ 172 - *gopher.nvim-gotests-named* 173 - 174 - if you prefare using named tests, you can enable it in the config. 175 - but you would need to install `gotests@develop` because stable version doesn't support this feature. 176 - you can do it with: 177 - >lua 178 - -- simply run go get in your shell: 179 - go install github.com/cweill/gotests/...@develop 193 + You can also specify the template to use for generating the tests. See |gopher.nvim-config| 194 + More details about templates can be found at: https://github.com/cweill/gotests 180 195 181 - -- if you want to install it within neovim, you can use one of this: 182 - 183 - vim.fn.jobstart("go install github.com/cweill/gotests/...@develop") 184 - 185 - -- or if you want to use mason: 186 - require("mason-tool-installer").setup { 187 - ensure_installed = { 188 - { "gotests", version = "develop" }, 189 - } 190 - } 191 - < 192 - 193 - if you choose to install `gotests` within neovim, i recommend adding it to your `build` section in your |lazy.nvim| 196 + If you prefer named tests, you can enable them in |gopher.nvim-config|. 194 197 195 198 196 199 ============================================================================== 197 200 ------------------------------------------------------------------------------ 198 201 *gopher.nvim-iferr* 199 - if you're using `iferr` tool, this module provides a way to automatically insert `if err != nil` check. 202 + 203 + `iferr` provides a way to way to automatically insert `if err != nil` check. 204 + If you want to change `-message` option of `iferr` tool, see |gopher.nvim-config| 205 + 200 206 Usage ~ 201 - execute `:GoIfErr` near any err variable to insert the check 207 + Execute `:GoIfErr` near any `err` variable to insert the check 202 208 203 209 204 210 ============================================================================== 205 211 ------------------------------------------------------------------------------ 206 212 *gopher.nvim-comments* 207 - Usage ~ 208 - Execute `:GoCmt` to generate a comment for the current function/method/struct/etc on this line. 213 + 209 214 This module provides a way to generate comments for Go code. 210 215 211 - 212 - ============================================================================== 213 - ------------------------------------------------------------------------------ 214 - *gopher.nvim-dap* 215 - This module sets up `nvim-dap` for Go. 216 216 Usage ~ 217 - just call `require("gopher.dap").setup()`, and you're good to go. 217 + Set cursor on line with function/method/struct/etc and run `:GoCmt` to generate a comment. 218 218 219 219 220 220 vim:tw=78:ts=8:noet:ft=help:norl:
-33
lua/gopher/_utils/health_util.lua
··· 1 - local h = vim.health or require "health" 2 - local health = {} 3 - 4 - health.start = h.start or h.report_start 5 - health.ok = h.ok or h.report_ok 6 - health.warn = h.warn or h.report_warn 7 - health.error = h.error or h.report_error 8 - health.info = h.info or h.report_info 9 - 10 - ---@param module string 11 - ---@return boolean 12 - function health.is_lualib_found(module) 13 - local is_found, _ = pcall(require, module) 14 - return is_found 15 - end 16 - 17 - ---@param bin string 18 - ---@return boolean 19 - function health.is_binary_found(bin) 20 - if vim.fn.executable(bin) == 1 then 21 - return true 22 - end 23 - return false 24 - end 25 - 26 - ---@param ft string 27 - ---@return boolean 28 - function health.is_treesitter_parser_available(ft) 29 - local ok, parser = pcall(vim.treesitter.get_parser, 0, ft) 30 - return ok and parser ~= nil 31 - end 32 - 33 - return health
+24 -17
lua/gopher/_utils/init.lua
··· 3 3 local utils = {} 4 4 5 5 ---@param msg string 6 - ---@param lvl number 7 - function utils.deferred_notify(msg, lvl) 8 - vim.defer_fn(function() 9 - vim.notify(msg, lvl, { 10 - title = c.___plugin_name, 11 - }) 12 - log.debug(msg) 13 - end, 0) 14 - end 15 - 16 - ---@param msg string 17 6 ---@param lvl? number 18 7 function utils.notify(msg, lvl) 19 8 lvl = lvl or vim.log.levels.INFO 20 9 vim.notify(msg, lvl, { 10 + ---@diagnostic disable-next-line:undefined-field 21 11 title = c.___plugin_name, 22 12 }) 23 13 log.debug(msg) 24 14 end 25 15 26 - -- safe require 27 - ---@param module string module name 28 - function utils.sreq(module) 29 - local ok, m = pcall(require, module) 30 - assert(ok, string.format("gopher.nvim dependency error: %s not installed", module)) 31 - return m 16 + ---@param path string 17 + ---@return string 18 + function utils.readfile_joined(path) 19 + return table.concat(vim.fn.readfile(path), "\n") 20 + end 21 + 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) 29 + end 30 + end 31 + return res 32 + end 33 + 34 + ---@param s string 35 + ---@return string 36 + function utils.trimend(s) 37 + local r, _ = string.gsub(s, "%s+$", "") 38 + return r 32 39 end 33 40 34 41 return utils
+2 -1
lua/gopher/_utils/log.lua
··· 18 18 19 19 local config = { 20 20 -- Name of the plugin. Prepended to log messages 21 + ---@diagnostic disable-next-line:undefined-field 21 22 name = c.___plugin_name, 22 23 23 24 -- Should print the output to neovim while running ··· 91 92 local log_at_level = function(level_config, message_maker, ...) 92 93 -- Return early if we're below the current_log_level 93 94 -- 94 - -- the log level source get from config directly because otherwise it doesnt work 95 + -- the log level source get from config directly because otherwise it doesn't work 95 96 if level_config.level < c.log_level then 96 97 return 97 98 end
+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
+10 -12
lua/gopher/_utils/runner/gocmd.lua lua/gopher/_utils/gocmd.lua
··· 25 25 26 26 ---@param subcmd string 27 27 ---@param args string[] 28 - ---@return string[]|nil 28 + ---@return string 29 29 function gocmd.run(subcmd, args) 30 - if #args == 0 then 31 - error "please provice any arguments" 30 + if #args == 0 and subcmd ~= "generate" then 31 + error "please provide any arguments" 32 32 end 33 33 34 34 if subcmd == "get" then ··· 39 39 args = if_generate(args) 40 40 end 41 41 42 - return r.sync(c.go, { 43 - args = { subcmd, unpack(args) }, 44 - on_exit = function(data, status) 45 - if status ~= 0 then 46 - error("gocmd failed: " .. data) 47 - end 48 - u.notify(c.go .. " " .. subcmd .. " successful runned") 49 - end, 50 - }) 42 + local rs = r.sync { c.go, subcmd, unpack(args) } 43 + if rs.code ~= 0 then 44 + error("go " .. subcmd .. " failed: " .. rs.stderr) 45 + end 46 + 47 + u.notify(c.go .. " " .. subcmd .. " ran successful") 48 + return rs.stdout 51 49 end 52 50 53 51 return gocmd
-33
lua/gopher/_utils/runner/init.lua
··· 1 - local Job = require "plenary.job" 2 - local runner = {} 3 - 4 - ---@class gopher.RunnerOpts 5 - ---@field args? string[] 6 - ---@field cwd? string? 7 - ---@field on_exit? fun(data:string, status:number) 8 - 9 - ---@param cmd string 10 - ---@param opts gopher.RunnerOpts 11 - ---@return string[]|nil 12 - function runner.sync(cmd, opts) 13 - local output 14 - Job:new({ 15 - command = cmd, 16 - args = opts.args, 17 - cwd = opts.cwd, 18 - on_stderr = function(_, data) 19 - vim.print(data) 20 - end, 21 - on_exit = function(data, status) 22 - output = data:result() 23 - vim.schedule(function() 24 - if opts.on_exit then 25 - opts.on_exit(output, status) 26 - end 27 - end) 28 - end, 29 - }):sync(60000 --[[1 min]]) 30 - return output 31 - end 32 - 33 - return runner
+136
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 + func = [[ 15 + [(function_declaration name: (identifier) @_name) 16 + (method_declaration name: (field_identifier) @_name)] 17 + ]], 18 + package = [[ 19 + (package_identifier) @_name 20 + ]], 21 + interface = [[ 22 + (type_spec 23 + name: (type_identifier) @_name 24 + type: (interface_type)) 25 + ]], 26 + } 27 + 28 + ---@param parent_type string[] 29 + ---@param node TSNode 30 + ---@return TSNode? 31 + local function get_parrent_node(parent_type, node) 32 + ---@type TSNode? 33 + local current = node 34 + while current do 35 + if vim.tbl_contains(parent_type, current:type()) then 36 + break 37 + end 38 + 39 + current = current:parent() 40 + if current == nil then 41 + return nil 42 + end 43 + end 44 + return current 45 + end 46 + 47 + ---@param query vim.treesitter.Query 48 + ---@param node TSNode 49 + ---@param bufnr integer 50 + ---@return {name:string, is_varstruct:boolean} 51 + local function get_captures(query, node, bufnr) 52 + local res = {} 53 + for id, _node in query:iter_captures(node, bufnr) do 54 + if query.captures[id] == "_name" then 55 + res["name"] = vim.treesitter.get_node_text(_node, bufnr) 56 + end 57 + 58 + if query.captures[id] == "_var" then 59 + res["is_varstruct"] = true 60 + end 61 + end 62 + 63 + return res 64 + end 65 + 66 + ---@class gopher.TsResult 67 + ---@field name string 68 + ---@field start_line integer 69 + ---@field end_line integer 70 + ---@field is_varstruct boolean 71 + 72 + ---@param bufnr integer 73 + ---@param parent_type string[] 74 + ---@param query string 75 + ---@return gopher.TsResult 76 + local function do_stuff(bufnr, parent_type, query) 77 + if not vim.treesitter.get_parser(bufnr, "go") then 78 + error "No treesitter parser found for go" 79 + end 80 + 81 + local node = vim.treesitter.get_node { 82 + bufnr = bufnr, 83 + } 84 + if not node then 85 + error "No nodes found under cursor" 86 + end 87 + 88 + local parent_node = get_parrent_node(parent_type, node) 89 + if not parent_node then 90 + error "No parent node found under cursor" 91 + end 92 + 93 + local q = vim.treesitter.query.parse("go", query) 94 + local res = get_captures(q, parent_node, bufnr) 95 + assert(res.name ~= nil, "No capture name found") 96 + 97 + local start_row, _, end_row, _ = parent_node:range() 98 + res["start_line"] = start_row + 1 99 + res["end_line"] = end_row + 1 100 + 101 + return res 102 + end 103 + 104 + ---@param bufnr integer 105 + function ts.get_struct_under_cursor(bufnr) 106 + --- should be both type_spec and type_declaration 107 + --- because in cases like `type ( T struct{}, U strict{} )` 108 + --- i will be choosing always last struct in the list 109 + --- 110 + --- var_declaration is for cases like `var x struct{}` 111 + --- short_var_declaration is for cases like `x := struct{}{}` 112 + return do_stuff(bufnr, { 113 + "type_spec", 114 + "type_declaration", 115 + "var_declaration", 116 + "short_var_declaration", 117 + }, queries.struct) 118 + end 119 + 120 + ---@param bufnr integer 121 + function ts.get_func_under_cursor(bufnr) 122 + --- since this handles both and funcs and methods we should check for both parent nodes 123 + return do_stuff(bufnr, { "function_declaration", "method_declaration" }, queries.func) 124 + end 125 + 126 + ---@param bufnr integer 127 + function ts.get_package_under_cursor(bufnr) 128 + return do_stuff(bufnr, { "package_clause" }, queries.package) 129 + end 130 + 131 + ---@param bufnr integer 132 + function ts.get_interface_under_cursor(bufnr) 133 + return do_stuff(bufnr, { "type_declaration" }, queries.interface) 134 + end 135 + 136 + return ts
-104
lua/gopher/_utils/ts/init.lua
··· 1 - ---@diagnostic disable: param-type-mismatch 2 - local nodes = require "gopher._utils.ts.nodes" 3 - local u = require "gopher._utils" 4 - local ts = { 5 - querys = { 6 - struct_block = [[((type_declaration (type_spec name:(type_identifier) @struct.name type: (struct_type)))@struct.declaration)]], 7 - em_struct_block = [[(field_declaration name:(field_identifier)@struct.name type: (struct_type)) @struct.declaration]], 8 - package = [[(package_clause (package_identifier)@package.name)@package.clause]], 9 - interface = [[((type_declaration (type_spec name:(type_identifier) @interface.name type:(interface_type)))@interface.declaration)]], 10 - method_name = [[((method_declaration receiver: (parameter_list)@method.receiver name: (field_identifier)@method.name body:(block))@method.declaration)]], 11 - func = [[((function_declaration name: (identifier)@function.name) @function.declaration)]], 12 - }, 13 - } 14 - 15 - ---@return table 16 - local function get_name_defaults() 17 - return { 18 - ["func"] = "function", 19 - ["if"] = "if", 20 - ["else"] = "else", 21 - ["for"] = "for", 22 - } 23 - end 24 - 25 - ---@param row string 26 - ---@param col string 27 - ---@param bufnr string|nil 28 - ---@param do_notify boolean|nil 29 - ---@return table|nil 30 - function ts.get_struct_node_at_pos(row, col, bufnr, do_notify) 31 - local notify = do_notify or true 32 - local query = ts.querys.struct_block .. " " .. ts.querys.em_struct_block 33 - local bufn = bufnr or vim.api.nvim_get_current_buf() 34 - local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col) 35 - if ns == nil then 36 - if notify then 37 - u.deferred_notify("struct not found", vim.log.levels.WARN) 38 - end 39 - else 40 - return ns[#ns] 41 - end 42 - end 43 - 44 - ---@param row string 45 - ---@param col string 46 - ---@param bufnr string|nil 47 - ---@param do_notify boolean|nil 48 - ---@return table|nil 49 - function ts.get_func_method_node_at_pos(row, col, bufnr, do_notify) 50 - local notify = do_notify or true 51 - local query = ts.querys.func .. " " .. ts.querys.method_name 52 - local bufn = bufnr or vim.api.nvim_get_current_buf() 53 - local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col) 54 - if ns == nil then 55 - if notify then 56 - u.deferred_notify("function not found", vim.log.levels.WARN) 57 - end 58 - else 59 - return ns[#ns] 60 - end 61 - end 62 - 63 - ---@param row string 64 - ---@param col string 65 - ---@param bufnr string|nil 66 - ---@param do_notify boolean|nil 67 - ---@return table|nil 68 - function ts.get_package_node_at_pos(row, col, bufnr, do_notify) 69 - local notify = do_notify or true 70 - -- stylua: ignore 71 - if row > 10 then return end 72 - local query = ts.querys.package 73 - local bufn = bufnr or vim.api.nvim_get_current_buf() 74 - local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col) 75 - if ns == nil then 76 - if notify then 77 - u.deferred_notify("package not found", vim.log.levels.WARN) 78 - return nil 79 - end 80 - else 81 - return ns[#ns] 82 - end 83 - end 84 - 85 - ---@param row string 86 - ---@param col string 87 - ---@param bufnr string|nil 88 - ---@param do_notify boolean|nil 89 - ---@return table|nil 90 - function ts.get_interface_node_at_pos(row, col, bufnr, do_notify) 91 - local notify = do_notify or true 92 - local query = ts.querys.interface 93 - local bufn = bufnr or vim.api.nvim_get_current_buf() 94 - local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col) 95 - if ns == nil then 96 - if notify then 97 - u.deferred_notify("interface not found", vim.log.levels.WARN) 98 - end 99 - else 100 - return ns[#ns] 101 - end 102 - end 103 - 104 - return ts
-143
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.query.parse(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.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.deferred_notify( 125 - "Unable to find any nodes. Place your cursor on a go symbol and try again", 126 - vim.log.levels.DEBUG 127 - ) 128 - return nil 129 - end 130 - 131 - nodes = M.sort_nodes(M.intersect_nodes(nodes, row, col)) 132 - if nodes == nil or #nodes == 0 then 133 - u.deferred_notify( 134 - "Unable to find any nodes at pos. " .. tostring(row) .. ":" .. tostring(col), 135 - vim.log.levels.DEBUG 136 - ) 137 - return nil 138 - end 139 - 140 - return nodes 141 - end 142 - 143 - return M
+39 -41
lua/gopher/comment.lua
··· 1 1 ---@toc_entry Generate comments 2 2 ---@tag gopher.nvim-comments 3 - ---@usage Execute `:GoCmt` to generate a comment for the current function/method/struct/etc on this line. 4 - ---@text This module provides a way to generate comments for Go code. 3 + ---@text 4 + --- This module provides a way to generate comments for Go code. 5 + --- 6 + ---@usage Set cursor on line with function/method/struct/etc and run `:GoCmt` to generate a comment. 5 7 8 + local ts = require "gopher._utils.ts" 6 9 local log = require "gopher._utils.log" 10 + local comment = {} 7 11 8 - local function generate(row, col) 9 - local ts_utils = require "gopher._utils.ts" 10 - local comment, ns = nil, nil 12 + ---@param name string 13 + ---@return string 14 + ---@dochide 15 + local function template(name) 16 + return "// " .. name .. " " 17 + end 11 18 12 - ns = ts_utils.get_package_node_at_pos(row, col, nil, false) 13 - if ns ~= nil then 14 - comment = "// Package " .. ns.name .. " provides " .. ns.name 15 - return comment, ns 19 + ---@param bufnr integer 20 + ---@return string 21 + ---@dochide 22 + local function generate(bufnr) 23 + local s_ok, s_res = pcall(ts.get_struct_under_cursor, bufnr) 24 + if s_ok then 25 + return template(s_res.name) 16 26 end 17 27 18 - ns = ts_utils.get_struct_node_at_pos(row, col, nil, false) 19 - if ns ~= nil then 20 - comment = "// " .. ns.name .. " " .. ns.type .. " " 21 - return comment, ns 28 + local f_ok, f_res = pcall(ts.get_func_under_cursor, bufnr) 29 + if f_ok then 30 + return template(f_res.name) 22 31 end 23 32 24 - ns = ts_utils.get_func_method_node_at_pos(row, col, nil, false) 25 - if ns ~= nil then 26 - comment = "// " .. ns.name .. " " .. ns.type .. " " 27 - return comment, ns 33 + local i_ok, i_res = pcall(ts.get_interface_under_cursor, bufnr) 34 + if i_ok then 35 + return template(i_res.name) 28 36 end 29 37 30 - ns = ts_utils.get_interface_node_at_pos(row, col, nil, false) 31 - if ns ~= nil then 32 - comment = "// " .. ns.name .. " " .. ns.type .. " " 33 - return comment, ns 38 + local p_ok, p_res = pcall(ts.get_package_under_cursor, bufnr) 39 + if p_ok then 40 + return "// Package " .. p_res.name .. " provides " 34 41 end 35 42 36 - return "// ", {} 43 + return "// " 37 44 end 38 45 39 - return function() 40 - local row, col = unpack(vim.api.nvim_win_get_cursor(0)) 41 - local comment, ns = generate(row + 1, col + 1) 42 - 43 - log.debug("generated comment: " .. comment) 44 - 45 - vim.api.nvim_win_set_cursor(0, { 46 - ns.dim.s.r, 47 - ns.dim.s.c, 48 - }) 49 - 50 - ---@diagnostic disable-next-line: param-type-mismatch 51 - vim.fn.append(row - 1, comment) 52 - 53 - vim.api.nvim_win_set_cursor(0, { 54 - ns.dim.s.r, 55 - #comment + 1, 56 - }) 46 + function comment.comment() 47 + local bufnr = vim.api.nvim_get_current_buf() 48 + local cmt = generate(bufnr) 49 + log.debug("generated comment: " .. cmt) 57 50 58 - vim.cmd [[startinsert!]] 51 + local pos = vim.fn.getcurpos()[2] 52 + vim.fn.append(pos - 1, cmt) 53 + vim.fn.setpos(".", { 0, pos, #cmt }) 54 + vim.cmd "startinsert!" 59 55 end 56 + 57 + return comment
+46 -25
lua/gopher/config.lua
··· 1 - ---@toc_entry Configuration 2 - ---@tag gopher.nvim-config 3 - ---@text config it is the place where you can configure the plugin. 4 - --- also this is optional is you're ok with default settings. 5 - --- You can look at default options |gopher.nvim-config-defaults| 6 - 7 - ---@type gopher.Config 8 - ---@private 9 1 local config = {} 10 2 11 3 ---@tag gopher.nvim-config.ConfigGoTagTransform 12 4 ---@text Possible values for |gopher.Config|.gotag.transform: 13 5 --- 14 - ---@private 6 + ---@dochide 15 7 ---@alias gopher.ConfigGoTagTransform 16 8 ---| "snakecase" "GopherUser" -> "gopher_user" 17 9 ---| "camelcase" "GopherUser" -> "gopherUser" ··· 20 12 ---| "titlecase" "GopherUser" -> "Gopher User" 21 13 ---| "keep" keeps the original field name 22 14 23 - --minidoc_replace_start { 24 - 25 - ---@tag gopher.nvim-config-defaults 26 - ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section):gsub(">", ">lua") 27 - --- 15 + ---@toc_entry Config 16 + ---@tag gopher.nvim-config 17 + ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section) 28 18 ---@class gopher.Config 29 19 local default_config = { 30 - --minidoc_replace_end 31 - 32 - -- log level, you might consider using DEBUG or TRACE for degugging the plugin 20 + -- log level, you might consider using DEBUG or TRACE for debugging the plugin 33 21 ---@type number 34 22 log_level = vim.log.levels.INFO, 23 + 24 + -- timeout for running internal commands 25 + ---@type number 26 + timeout = 2000, 35 27 36 28 -- user specified paths to binaries 37 29 ---@class gopher.ConfigCommand ··· 41 33 gotests = "gotests", 42 34 impl = "impl", 43 35 iferr = "iferr", 44 - dlv = "dlv", 45 36 }, 46 37 ---@class gopher.ConfigGotests 47 38 gotests = { ··· 51 42 ---@type string|nil 52 43 template_dir = nil, 53 44 -- switch table tests from using slice to map (with test name for the key) 54 - -- works only with gotests installed from develop branch 55 45 named = false, 56 46 }, 57 47 ---@class gopher.ConfigGoTag 58 48 gotag = { 59 49 ---@type gopher.ConfigGoTagTransform 60 50 transform = "snakecase", 51 + 52 + -- default tags to add to struct fields 53 + default_tag = "json", 54 + }, 55 + iferr = { 56 + -- choose a custom error message 57 + ---@type string|nil 58 + message = nil, 61 59 }, 62 60 } 63 61 --minidoc_afterlines_end 64 62 65 63 ---@type gopher.Config 66 - ---@private 64 + ---@dochide 67 65 local _config = default_config 68 66 69 - -- I am kinda secret so don't tell anyone about me 70 - -- even dont use me 67 + -- I am kinda secret so don't tell anyone about me even dont use me 71 68 -- 72 - -- if you don't belive me that i am secret see 69 + -- if you don't believe me that i am secret see 73 70 -- the line below it says @private 74 71 ---@private 75 72 _config.___plugin_name = "gopher.nvim" ---@diagnostic disable-line: inject-field 76 73 77 74 ---@param user_config? gopher.Config 78 - ---@private 75 + ---@dochide 79 76 function config.setup(user_config) 80 - _config = vim.tbl_deep_extend("force", default_config, user_config or {}) 77 + vim.validate { user_config = { user_config, "table", true } } 78 + 79 + _config = vim.tbl_deep_extend("force", vim.deepcopy(default_config), user_config or {}) 80 + 81 + vim.validate { 82 + log_level = { _config.log_level, "number" }, 83 + timeout = { _config.timeout, "number" }, 84 + ["commands"] = { _config.commands, "table" }, 85 + ["commands.go"] = { _config.commands.go, "string" }, 86 + ["commands.gomodifytags"] = { _config.commands.gomodifytags, "string" }, 87 + ["commands.gotests"] = { _config.commands.gotests, "string" }, 88 + ["commands.impl"] = { _config.commands.impl, "string" }, 89 + ["commands.iferr"] = { _config.commands.iferr, "string" }, 90 + ["gotests"] = { _config.gotests, "table" }, 91 + ["gotests.template"] = { _config.gotests.template, "string" }, 92 + ["gotests.template_dir"] = { _config.gotests.template, "string", true }, 93 + ["gotests.named"] = { _config.gotests.named, "boolean" }, 94 + ["gotag"] = { _config.gotag, "table" }, 95 + ["gotag.transform"] = { _config.gotag.transform, "string" }, 96 + ["gotag.default_tag"] = { _config.gotag.default_tag, "string" }, 97 + ["iferr"] = { _config.iferr, "table" }, 98 + ["iferr.message"] = { _config.iferr.message, "string", true }, 99 + } 81 100 end 82 101 83 102 setmetatable(config, { ··· 86 105 end, 87 106 }) 88 107 108 + ---@dochide 109 + ---@return gopher.Config 89 110 return config
-129
lua/gopher/dap.lua
··· 1 - ---@toc_entry Setup `nvim-dap` for Go 2 - ---@tag gopher.nvim-dap 3 - ---@text This module sets up `nvim-dap` for Go. 4 - ---@usage just call `require("gopher.dap").setup()`, and you're good to go. 5 - 6 - local c = require "gopher.config" 7 - local dap = {} 8 - 9 - dap.adapter = function(callback, config) 10 - local host = config.host or "127.0.0.1" 11 - local port = config.port or "38697" 12 - local addr = string.format("%s:%s", host, port) 13 - 14 - local handle, pid_or_err 15 - local stdout = assert(vim.loop.new_pipe(false)) 16 - local opts = { 17 - stdio = { nil, stdout }, 18 - args = { "dap", "-l", addr }, 19 - detached = true, 20 - } 21 - 22 - handle, pid_or_err = vim.loop.spawn(c.commands.dlv, opts, function(status) 23 - if not stdout or not handle then 24 - return 25 - end 26 - 27 - stdout:close() 28 - handle:close() 29 - if status ~= 0 then 30 - print("dlv exited with code", status) 31 - end 32 - end) 33 - 34 - assert(handle, "Error running dlv: " .. tostring(pid_or_err)) 35 - if stdout then 36 - stdout:read_start(function(err, chunk) 37 - assert(not err, err) 38 - if chunk then 39 - vim.schedule(function() 40 - require("dap.repl").append(chunk) 41 - end) 42 - end 43 - end) 44 - end 45 - 46 - -- wait for delve to start 47 - vim.defer_fn(function() 48 - callback { type = "server", host = "127.0.0.1", port = port } 49 - end, 100) 50 - end 51 - 52 - local function args_input() 53 - vim.ui.input({ prompt = "Args: " }, function(input) 54 - return vim.split(input or "", " ") 55 - end) 56 - end 57 - 58 - local function get_arguments() 59 - local co = coroutine.running() 60 - if co then 61 - return coroutine.create(function() 62 - local args = args_input() 63 - coroutine.resume(co, args) 64 - end) 65 - else 66 - return args_input() 67 - end 68 - end 69 - 70 - dap.configuration = { 71 - { 72 - type = "go", 73 - name = "Debug", 74 - request = "launch", 75 - program = "${file}", 76 - }, 77 - { 78 - type = "go", 79 - name = "Debug (Arguments)", 80 - request = "launch", 81 - program = "${file}", 82 - args = get_arguments, 83 - }, 84 - { 85 - type = "go", 86 - name = "Debug Package", 87 - request = "launch", 88 - program = "${fileDirname}", 89 - }, 90 - { 91 - type = "go", 92 - name = "Attach", 93 - mode = "local", 94 - request = "attach", 95 - processId = require("dap.utils").pick_process, 96 - }, 97 - { 98 - type = "go", 99 - name = "Debug test", 100 - request = "launch", 101 - mode = "test", 102 - program = "${file}", 103 - }, 104 - { 105 - type = "go", 106 - name = "Debug test (go.mod)", 107 - request = "launch", 108 - mode = "test", 109 - program = "./${relativeFileDirname}", 110 - }, 111 - } 112 - 113 - -- sets ups nvim-dap for Go in one function call. 114 - function dap.setup() 115 - vim.deprecate( 116 - "gopher.dap", 117 - "you might consider setting up `nvim-dap` manually, or using another plugin(https://github.com/leoluz/nvim-dap-go)", 118 - "v0.1.6", 119 - "gopher" 120 - ) 121 - 122 - local ok, d = pcall(require, "dap") 123 - assert(ok, "gopher.nvim dependency error: dap not installed") 124 - 125 - d.adapters.go = dap.adapter 126 - d.configurations.go = dap.configuration 127 - end 128 - 129 - return dap
+17 -46
lua/gopher/gotests.lua
··· 2 2 ---@tag gopher.nvim-gotests 3 3 ---@text gotests is utilizing the `gotests` tool to generate unit tests boilerplate. 4 4 ---@usage 5 - --- - generate unit test for spesisfic function/method 6 - --- - to specift the function/method put your cursor on it 7 - --- - run `:GoTestAdd` 5 + --- - Generate unit test for specific function/method: 6 + --- 1. Place your cursor on the desired function/method. 7 + --- 2. Run `:GoTestAdd` 8 8 --- 9 - --- - generate unit tests for all functions/methods in current file 9 + --- - Generate unit tests for *all* functions/methods in current file: 10 10 --- - run `:GoTestsAll` 11 11 --- 12 - --- - generate unit tests only for exported(public) functions/methods 12 + --- - Generate unit tests *only* for *exported(public)* functions/methods: 13 13 --- - run `:GoTestsExp` 14 14 --- 15 - --- you can also specify the template to use for generating the tests. see |gopher.nvim-config| 16 - --- more details about templates can be found at: https://github.com/cweill/gotests 15 + --- You can also specify the template to use for generating the tests. See |gopher.nvim-config| 16 + --- More details about templates can be found at: https://github.com/cweill/gotests 17 17 --- 18 - 19 - ---@tag gopher.nvim-gotests-named 20 - ---@text 21 - --- if you prefare using named tests, you can enable it in the config. 22 - --- but you would need to install `gotests@develop` because stable version doesn't support this feature. 23 - --- you can do it with: 24 - --- >lua 25 - --- -- simply run go get in your shell: 26 - --- go install github.com/cweill/gotests/...@develop 27 - --- 28 - --- -- if you want to install it within neovim, you can use one of this: 29 - --- 30 - --- vim.fn.jobstart("go install github.com/cweill/gotests/...@develop") 31 - --- 32 - --- -- or if you want to use mason: 33 - --- require("mason-tool-installer").setup { 34 - --- ensure_installed = { 35 - --- { "gotests", version = "develop" }, 36 - --- } 37 - --- } 38 - --- < 39 - --- 40 - --- if you choose to install `gotests` within neovim, i recommend adding it to your `build` section in your |lazy.nvim| 18 + --- If you prefer named tests, you can enable them in |gopher.nvim-config|. 41 19 42 20 local c = require "gopher.config" 43 21 local ts_utils = require "gopher._utils.ts" ··· 47 25 local gotests = {} 48 26 49 27 ---@param args table 50 - ---@private 28 + ---@dochide 51 29 local function add_test(args) 52 30 if c.gotests.named then 53 31 table.insert(args, "-named") ··· 68 46 69 47 log.debug("generating tests with args: ", args) 70 48 71 - return r.sync(c.commands.gotests, { 72 - args = args, 73 - on_exit = function(data, status) 74 - if not status == 0 then 75 - error("gotests failed: " .. data) 76 - end 49 + local rs = r.sync { c.commands.gotests, unpack(args) } 50 + if rs.code ~= 0 then 51 + error("gotests failed: " .. rs.stderr) 52 + end 77 53 78 - u.notify "unit test(s) generated" 79 - end, 80 - }) 54 + u.notify "unit test(s) generated" 81 55 end 82 56 83 57 -- generate unit test for one function 84 58 function gotests.func_test() 85 - local ns = ts_utils.get_func_method_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0))) 86 - if ns == nil or ns.name == nil then 87 - u.notify("cursor on func/method and execute the command again", vim.log.levels.WARN) 88 - return 89 - end 59 + local bufnr = vim.api.nvim_get_current_buf() 60 + local func = ts_utils.get_func_under_cursor(bufnr) 90 61 91 - add_test { "-only", ns.name } 62 + add_test { "-only", func.name } 92 63 end 93 64 94 65 -- generate unit tests for all functions in current file
+40 -28
lua/gopher/health.lua
··· 1 1 local health = {} 2 2 local cmd = require("gopher.config").commands 3 - local u = require "gopher._utils.health_util" 4 3 5 4 local deps = { 6 5 plugin = { 7 - { lib = "dap", msg = "required for `gopher.dap`", optional = true }, 8 - { lib = "plenary", msg = "required for everyting in gopher.nvim", optional = false }, 9 - { lib = "nvim-treesitter", msg = "required for everyting in gopher.nvim", optional = false }, 6 + { lib = "nvim-treesitter", msg = "required for everything in gopher.nvim" }, 10 7 }, 11 8 bin = { 12 9 { ··· 14 11 msg = "required for `:GoGet`, `:GoMod`, `:GoGenerate`, `:GoWork`, `:GoInstallDeps`", 15 12 optional = false, 16 13 }, 17 - { bin = cmd.gomodifytags, msg = "required for `:GoTagAdd`, `:GoTagRm`", optional = false }, 18 - { bin = cmd.impl, msg = "required for `:GoImpl`", optional = false }, 19 - { bin = cmd.iferr, msg = "required for `:GoIfErr`", optional = false }, 14 + { bin = cmd.gomodifytags, msg = "required for `:GoTagAdd`, `:GoTagRm`", optional = true }, 15 + { bin = cmd.impl, msg = "required for `:GoImpl`", optional = true }, 16 + { bin = cmd.iferr, msg = "required for `:GoIfErr`", optional = true }, 20 17 { 21 18 bin = cmd.gotests, 22 19 msg = "required for `:GoTestAdd`, `:GoTestsAll`, `:GoTestsExp`", 23 - optional = false, 20 + optional = true, 24 21 }, 25 - { bin = cmd.dlv, msg = "required for debugging, (`nvim-dap`, `gopher.dap`)", optional = true }, 26 22 }, 27 23 treesitter = { 28 - { parser = "go", msg = "required for `gopher.nvim`", optional = false }, 24 + { parser = "go", msg = "required for `gopher.nvim`" }, 29 25 }, 30 26 } 31 27 28 + ---@param module string 29 + ---@return boolean 30 + local function is_lualib_found(module) 31 + local is_found, _ = pcall(require, module) 32 + return is_found 33 + end 34 + 35 + ---@param bin string 36 + ---@return boolean 37 + local function is_binary_found(bin) 38 + return vim.fn.executable(bin) == 1 39 + end 40 + 41 + ---@param ft string 42 + ---@return boolean 43 + local function is_treesitter_parser_available(ft) 44 + local ok, parser = pcall(vim.treesitter.get_parser, 0, ft) 45 + return ok and parser ~= nil 46 + end 47 + 32 48 function health.check() 33 - u.start "required plugins" 49 + vim.health.start "required plugins" 34 50 for _, plugin in ipairs(deps.plugin) do 35 - if u.is_lualib_found(plugin.lib) then 36 - u.ok(plugin.lib .. " installed") 51 + if is_lualib_found(plugin.lib) then 52 + vim.health.ok(plugin.lib .. " installed") 37 53 else 38 - if plugin.optional then 39 - u.warn(plugin.lib .. " not found, " .. plugin.msg) 40 - else 41 - u.error(plugin.lib .. " not found, " .. plugin.msg) 42 - end 54 + vim.health.error(plugin.lib .. " not found, " .. plugin.msg) 43 55 end 44 56 end 45 57 46 - u.start "required binaries" 47 - u.info "all those binaries can be installed by `:GoInstallDeps`" 58 + vim.health.start "required binaries" 59 + vim.health.info "all those binaries can be installed by `:GoInstallDeps`" 48 60 for _, bin in ipairs(deps.bin) do 49 - if u.is_binary_found(bin.bin) then 50 - u.ok(bin.bin .. " installed") 61 + if is_binary_found(bin.bin) then 62 + vim.health.ok(bin.bin .. " installed") 51 63 else 52 64 if bin.optional then 53 - u.warn(bin.bin .. " not found, " .. bin.msg) 65 + vim.health.warn(bin.bin .. " not found, " .. bin.msg) 54 66 else 55 - u.error(bin.bin .. " not found, " .. bin.msg) 67 + vim.health.error(bin.bin .. " not found, " .. bin.msg) 56 68 end 57 69 end 58 70 end 59 71 60 - u.start "required treesitter parsers" 72 + vim.health.start "required treesitter parsers" 61 73 for _, parser in ipairs(deps.treesitter) do 62 - if u.is_treesitter_parser_available(parser.parser) then 63 - u.ok(parser.parser .. " parser installed") 74 + if is_treesitter_parser_available(parser.parser) then 75 + vim.health.ok(parser.parser .. " parser installed") 64 76 else 65 - u.error(parser.parser .. " parser not found, " .. parser.msg) 77 + vim.health.error(parser.parser .. " parser not found, " .. parser.msg) 66 78 end 67 79 end 68 80 end
+28 -12
lua/gopher/iferr.lua
··· 1 + -- Thanks https://github.com/koron/iferr for vim implementation 2 + 1 3 ---@toc_entry Iferr 2 4 ---@tag gopher.nvim-iferr 3 - ---@text if you're using `iferr` tool, this module provides a way to automatically insert `if err != nil` check. 4 - ---@usage execute `:GoIfErr` near any err variable to insert the check 5 + ---@text 6 + --- `iferr` provides a way to way to automatically insert `if err != nil` check. 7 + --- If you want to change `-message` option of `iferr` tool, see |gopher.nvim-config| 8 + --- 9 + ---@usage Execute `:GoIfErr` near any `err` variable to insert the check 5 10 6 11 local c = require "gopher.config" 12 + local u = require "gopher._utils" 13 + local r = require "gopher._utils.runner" 7 14 local log = require "gopher._utils.log" 8 15 local iferr = {} 9 16 10 - -- That's Lua implementation: github.com/koron/iferr 11 17 function iferr.iferr() 12 - local boff = vim.fn.wordcount().cursor_bytes 18 + local curb = vim.fn.wordcount().cursor_bytes 13 19 local pos = vim.fn.getcurpos()[2] 20 + local fpath = vim.fn.expand "%" 14 21 15 - local data = vim.fn.systemlist((c.commands.iferr .. " -pos " .. boff), vim.fn.bufnr "%") 16 - if vim.v.shell_error ~= 0 then 17 - if string.find(data[1], "no functions at") then 18 - vim.print "no function found" 19 - log.warn("iferr: no function at " .. boff) 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) 20 36 return 21 37 end 22 38 23 - log.error("failed. output: " .. vim.inspect(data)) 24 - error("iferr failed: " .. vim.inspect(data)) 39 + log.error("ferr: failed. output: " .. rs.stderr) 40 + error("iferr failed: " .. rs.stderr) 25 41 end 26 42 27 - vim.fn.append(pos, data) 43 + vim.fn.append(pos, u.remove_empty_lines(vim.split(rs.stdout, "\n"))) 28 44 vim.cmd [[silent normal! j=2j]] 29 45 vim.fn.setpos(".", pos) 30 46 end
+40 -66
lua/gopher/impl.lua
··· 1 1 ---@toc_entry Auto implementation of interface methods 2 2 ---@tag gopher.nvim-impl 3 - ---@text impl is utilizing the `impl` tool to generate method stubs for interfaces. 4 - ---@usage 5 - --- 1. put your coursor on the struct on which you want implement the interface 6 - --- and run `:GoImpl io.Reader` 7 - --- which will automatically choose the reciver for the methods and 8 - --- implement the `io.Reader` interface 9 - --- 2. same as previous but with custom receiver, so put your coursor on the struct 10 - --- run `:GoImpl w io.Writer` 11 - --- where `w` is the receiver and `io.Writer` is the interface 12 - --- 3. specift receiver, struct, and interface 13 - --- there's no need to put your coursor on the struct if you specify all arguments 14 - --- `:GoImpl r RequestReader io.Reader` 15 - --- where `r` is the receiver, `RequestReader` is the struct and `io.Reader` is the interface 3 + ---@text 4 + --- Integration of `impl` tool to generate method stubs for interfaces. 5 + --- 6 + ---@usage 1. Automatically implement an interface for a struct: 7 + --- - Place your cursor on the struct where you want to implement the interface. 8 + --- - Run `:GoImpl io.Reader` 9 + --- - This will automatically determine the receiver and implement the `io.Reader` interface. 10 + --- 11 + --- 2. Specify a custom receiver: 12 + --- - Place your cursor on the struct 13 + --- - Run `:GoImpl w io.Writer`, where: 14 + --- - `w` is the receiver. 15 + --- - `io.Writer` is the interface to implement. 16 + --- 17 + --- 3. Explicitly specify the receiver, struct, and interface: 18 + --- - No need to place the cursor on the struct if all arguments are provided. 19 + --- - Run `:GoImpl r RequestReader io.Reader`, where: 20 + --- - `r` is the receiver. 21 + --- - `RequestReader` is the struct. 22 + --- - `io.Reader` is the interface to implement. 16 23 --- 17 - --- simple example: 24 + --- Example: 18 25 --- >go 19 26 --- type BytesReader struct{} 20 27 --- // ^ put your cursor here ··· 22 29 --- 23 30 --- // this is what you will get 24 31 --- func (b *BytesReader) Read(p []byte) (n int, err error) { 25 - --- panic("not implemented") // TODO: Implement 32 + --- panic("not implemented") // TODO: Implement 26 33 --- } 27 34 --- < 28 35 ··· 32 39 local u = require "gopher._utils" 33 40 local impl = {} 34 41 35 - ---@return string 36 - ---@private 37 - local function get_struct() 38 - local ns = ts_utils.get_struct_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0))) 39 - if ns == nil then 40 - u.deferred_notify("put cursor on a struct or specify a receiver", vim.log.levels.INFO) 41 - return "" 42 - end 43 - 44 - vim.api.nvim_win_set_cursor(0, { 45 - ns.dim.e.r, 46 - ns.dim.e.c, 47 - }) 48 - 49 - return ns.name 50 - end 51 - 52 42 function impl.impl(...) 53 43 local args = { ... } 54 - local iface, recv_name = "", "" 55 - local recv = get_struct() 44 + local iface, recv = "", "" 45 + local bufnr = vim.api.nvim_get_current_buf() 56 46 57 - if #args == 0 then 58 - iface = vim.fn.input "impl: generating method stubs for interface: " 59 - vim.cmd "redraw!" 60 - if iface == "" then 61 - u.deferred_notify("usage: GoImpl f *File io.Reader", vim.log.levels.INFO) 62 - return 63 - end 64 - elseif #args == 1 then -- :GoImpl io.Reader 65 - recv = string.lower(recv) .. " *" .. recv 66 - vim.cmd "redraw!" 67 - iface = select(1, ...) 47 + if #args == 1 then -- :GoImpl io.Reader 48 + local st = ts_utils.get_struct_under_cursor(bufnr) 49 + iface = args[1] 50 + recv = string.lower(st.name) .. " *" .. st.name 68 51 elseif #args == 2 then -- :GoImpl w io.Writer 69 - recv_name = select(1, ...) 70 - recv = string.format("%s *%s", recv_name, recv) 71 - iface = select(#args, ...) 72 - elseif #args > 2 then 73 - iface = select(#args, ...) 74 - recv = select(#args - 1, ...) 75 - recv_name = select(#args - 2, ...) 76 - recv = string.format("%s %s", recv_name, recv) 52 + local st = ts_utils.get_struct_under_cursor(bufnr) 53 + iface = args[2] 54 + recv = args[1] .. " *" .. st.name 55 + elseif #args == 3 then -- :GoImpl r Struct io.Reader 56 + recv = args[1] .. " *" .. args[2] 57 + iface = args[3] 77 58 end 78 59 79 - local output = r.sync(c.impl, { 80 - args = { 81 - "-dir", 82 - vim.fn.fnameescape(vim.fn.expand "%:p:h" --[[@as string]]), 83 - recv, 84 - iface, 85 - }, 86 - on_exit = function(data, status) 87 - if not status == 0 then 88 - error("impl failed: " .. data) 89 - end 90 - end, 91 - }) 60 + local rs = r.sync { c.impl, "-dir", vim.fn.fnameescape(vim.fn.expand "%:p:h"), recv, iface } 61 + if rs.code ~= 0 then 62 + error("failed to implement interface: " .. rs.stderr) 63 + end 92 64 93 65 local pos = vim.fn.getcurpos()[2] 66 + local output = u.remove_empty_lines(vim.split(rs.stdout, "\n")) 67 + 94 68 table.insert(output, 1, "") 95 69 vim.fn.append(pos, output) 96 70 end
+24 -17
lua/gopher/init.lua
··· 1 - --- *gopher.nvim* 1 + --- *gopher.nvim* Enhance your golang experience 2 + --- 3 + --- MIT License Copyright (c) 2025 Oleksandr Smirnov 2 4 --- 3 5 --- ============================================================================== 4 6 --- 5 7 --- gopher.nvim is a minimalistic plugin for Go development in Neovim written in Lua. 6 8 --- It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim. 7 - 9 + --- 8 10 --- Table of Contents 9 - ---@tag gopher.nvim-table-of-contents 10 11 ---@toc 11 12 12 13 local log = require "gopher._utils.log" 13 14 local tags = require "gopher.struct_tags" 14 15 local tests = require "gopher.gotests" 15 - local gocmd = require("gopher._utils.runner.gocmd").run 16 + local gocmd = require("gopher._utils.gocmd").run 16 17 local gopher = {} 17 18 18 19 ---@toc_entry Setup 19 - ---@tag gopher.nvim-setup 20 - ---@text Setup function. This method simply merges default configs with opts table. 20 + ---@tag gopher.nvim-setup() 21 + ---@text Setup function. This method simply merges default config with opts table. 21 22 --- You can read more about configuration at |gopher.nvim-config| 22 - --- Calling this function is optional, if you ok with default settings. Look |gopher.nvim.config-defaults| 23 + --- Calling this function is optional, if you ok with default settings. 24 + --- See |gopher.nvim.config| 23 25 --- 24 - ---@usage `require("gopher").setup {}` (replace `{}` with your `config` table) 25 - ---@param user_config gopher.Config 26 + ---@usage >lua 27 + --- require("gopher").setup {} -- use default config or replace {} with your own 28 + --- < 29 + ---@param user_config gopher.Config See |gopher.nvim-config| 26 30 gopher.setup = function(user_config) 27 31 log.debug "setting up config" 28 32 require("gopher.config").setup(user_config) ··· 30 34 end 31 35 32 36 ---@toc_entry Install dependencies 33 - ---@tag gopher.nvim-install-deps 37 + ---@tag gopher.nvim-dependencies 34 38 ---@text Gopher.nvim implements most of its features using third-party tools. 35 - --- To install these tools, you can run `:GoInstallDeps` command 36 - --- or call `require("gopher").install_deps()` if you want ues lua api. 39 + --- To install these tools, you can run `:GoInstallDeps` command 40 + --- or call `require("gopher").install_deps()` if you want to use lua api. 41 + --- By default dependencies will be installed asynchronously, 42 + --- to install them synchronously pass `{sync = true}` as an argument. 37 43 gopher.install_deps = require("gopher.installer").install_deps 38 44 39 45 gopher.impl = require("gopher.impl").impl 40 46 gopher.iferr = require("gopher.iferr").iferr 41 - gopher.comment = require "gopher.comment" 47 + gopher.comment = require("gopher.comment").comment 42 48 43 49 gopher.tags = { 44 50 add = tags.add, 45 51 rm = tags.remove, 52 + clear = tags.clear, 46 53 } 47 54 48 55 gopher.test = { ··· 52 59 } 53 60 54 61 gopher.get = function(...) 55 - gocmd("get", { ... }) 62 + gocmd("get", ...) 56 63 end 57 64 58 65 gopher.mod = function(...) 59 - gocmd("mod", { ... }) 66 + gocmd("mod", ...) 60 67 end 61 68 62 69 gopher.generate = function(...) 63 - gocmd("generate", { ... }) 70 + gocmd("generate", ...) 64 71 end 65 72 66 73 gopher.work = function(...) 67 - gocmd("work", { ... }) 74 + gocmd("work", ...) 68 75 end 69 76 70 77 return gopher
+42 -21
lua/gopher/installer.lua
··· 1 1 local c = require("gopher.config").commands 2 2 local r = require "gopher._utils.runner" 3 3 local u = require "gopher._utils" 4 + local log = require "gopher._utils.log" 4 5 local installer = {} 5 6 6 7 local urls = { 7 - gomodifytags = "github.com/fatih/gomodifytags", 8 - impl = "github.com/josharian/impl", 9 - gotests = "github.com/cweill/gotests/...", 10 - iferr = "github.com/koron/iferr", 11 - dlv = "github.com/go-delve/delve/cmd/dlv", 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 12 } 13 13 14 - ---@param pkg string 15 - local function install(pkg) 16 - local url = urls[pkg] .. "@latest" 17 - r.sync(c.go, { 18 - args = { "install", url }, 19 - on_exit = function(data, status) 20 - if not status == 0 then 21 - error("go install failed: " .. data) 22 - return 23 - end 24 - u.notify("installed: " .. url) 25 - end, 26 - }) 14 + ---@param opt vim.SystemCompleted 15 + ---@param url string 16 + local function handle_intall_exit(opt, url) 17 + if opt.code ~= 0 then 18 + vim.schedule(function() 19 + u.notify("go install failed: " .. url) 20 + end) 21 + 22 + log.error("go install failed:", "url", url, "opt", vim.inspect(opt)) 23 + return 24 + end 25 + 26 + vim.schedule(function() 27 + u.notify("go install-ed: " .. url) 28 + end) 29 + end 30 + 31 + ---@param url string 32 + local function install(url) 33 + r.async({ c.go, "install", url }, function(opt) 34 + handle_intall_exit(opt, url) 35 + end) 36 + end 37 + 38 + ---@param url string 39 + local function install_sync(url) 40 + local rs = r.sync { c.go, "install", url } 41 + handle_intall_exit(rs, url) 27 42 end 28 43 29 44 ---Install required go deps 30 - function installer.install_deps() 31 - for pkg, _ in pairs(urls) do 32 - install(pkg) 45 + ---@param opts? {sync:boolean} 46 + function installer.install_deps(opts) 47 + opts = opts or {} 48 + for _, url in pairs(urls) do 49 + if opts.sync then 50 + install_sync(url) 51 + else 52 + install(url) 53 + end 33 54 end 34 55 end 35 56
+83 -73
lua/gopher/struct_tags.lua
··· 1 - ---@toc_entry Modifty struct tags 1 + ---@toc_entry Modify struct tags 2 2 ---@tag gopher.nvim-struct-tags 3 - ---@text struct-tags is utilizing the `gomodifytags` tool to add or remove tags to struct fields. 4 - ---@usage - put your coursor on the struct 5 - --- - run `:GoTagAdd json` to add json tags to struct fields 6 - --- - run `:GoTagRm json` to remove json tags to struct fields 3 + ---@text 4 + --- `struct_tags` is utilizing the `gomodifytags` tool to add or remove tags to struct fields. 7 5 --- 8 - --- note: if you dont spesify the tag it will use `json` as default 6 + ---@usage 7 + --- How to add/remove tags to struct fields: 8 + --- 1. Place cursor on the struct 9 + --- 2. Run `:GoTagAdd json` to add json tags to struct fields 10 + --- 3. Run `:GoTagRm json` to remove json tags to struct fields 9 11 --- 10 - --- simple example: 12 + --- To clear all tags from struct run: `:GoTagClear` 13 + --- 14 + --- NOTE: if you dont specify the tag it will use `json` as default 15 + --- 16 + --- Example: 11 17 --- >go 12 18 --- // before 13 19 --- type User struct { ··· 24 30 --- } 25 31 --- < 26 32 27 - local ts_utils = require "gopher._utils.ts" 33 + local ts = require "gopher._utils.ts" 28 34 local r = require "gopher._utils.runner" 29 35 local c = require "gopher.config" 36 + local u = require "gopher._utils" 37 + local log = require "gopher._utils.log" 30 38 local struct_tags = {} 31 39 32 - local function modify(...) 33 - local fpath = vim.fn.expand "%" ---@diagnostic disable-line: missing-parameter 34 - local ns = ts_utils.get_struct_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0))) 35 - if ns == nil then 36 - return 37 - end 40 + ---@param fpath string 41 + ---@param bufnr integer 42 + ---@param user_args string[] 43 + ---@dochide 44 + local function handle_tags(fpath, bufnr, user_args) 45 + local st = ts.get_struct_under_cursor(bufnr) 38 46 39 47 -- stylua: ignore 40 - local cmd_args = { 48 + local cmd = { 49 + c.commands.gomodifytags, 41 50 "-transform", c.gotag.transform, 42 51 "-format", "json", 43 52 "-file", fpath, 44 - "-w" 53 + "-w", 45 54 } 46 55 47 - -- by struct name of line pos 48 - if ns.name == nil then 49 - local _, csrow, _, _ = unpack(vim.fn.getpos ".") 50 - table.insert(cmd_args, "-line") 51 - table.insert(cmd_args, csrow) 56 + if st.is_varstruct then 57 + table.insert(cmd, "-line") 58 + table.insert(cmd, string.format("%d,%d", st.start_line, st.end_line)) 52 59 else 53 - table.insert(cmd_args, "-struct") 54 - table.insert(cmd_args, ns.name) 60 + table.insert(cmd, "-struct") 61 + table.insert(cmd, st.name) 55 62 end 56 63 57 - -- set user args for cmd 58 - local arg = { ... } 59 - for _, v in ipairs(arg) do 60 - table.insert(cmd_args, v) 64 + for _, v in ipairs(user_args) do 65 + table.insert(cmd, v) 61 66 end 62 67 63 - -- set default tag for "clear tags" 64 - if #arg == 1 and arg[1] ~= "-clear-tags" then 65 - table.insert(cmd_args, "json") 68 + local rs = r.sync(cmd) 69 + if rs.code ~= 0 then 70 + log.error("tags: failed to set tags " .. rs.stderr) 71 + error("failed to set tags " .. rs.stderr) 66 72 end 67 73 68 - local output = r.sync(c.commands.gomodifytags, { 69 - args = cmd_args, 70 - on_exit = function(data, status) 71 - if not status == 0 then 72 - error("gotag failed: " .. data) 73 - end 74 - end, 75 - }) 74 + local res = vim.json.decode(rs.stdout) 75 + if res["errors"] then 76 + log.error("tags: got an error " .. vim.inspect(res)) 77 + error("failed to set tags " .. vim.inspect(res["errors"])) 78 + end 76 79 77 - -- decode goted value 78 - local tagged = vim.json.decode(table.concat(output)) 79 - if 80 - tagged.errors ~= nil 81 - or tagged.lines == nil 82 - or tagged["start"] == nil 83 - or tagged["start"] == 0 84 - then 85 - error("failed to set tags " .. vim.inspect(tagged)) 80 + for i, v in ipairs(res["lines"]) do 81 + res["lines"][i] = u.trimend(v) 86 82 end 87 83 88 84 vim.api.nvim_buf_set_lines( 89 - 0, 90 - tagged.start - 1, 91 - tagged.start - 1 + #tagged.lines, 92 - false, 93 - tagged.lines 85 + bufnr, 86 + res["start"] - 1, 87 + res["start"] - 1 + #res["lines"], 88 + true, 89 + res["lines"] 94 90 ) 95 - vim.cmd "write" 96 91 end 97 92 98 - -- add tags to struct under cursor 99 - function struct_tags.add(...) 100 - local arg = { ... } 101 - if #arg == nil or arg == "" then 102 - arg = { "json" } 93 + ---@param args string[] 94 + ---@return string 95 + ---@dochide 96 + local function handler_user_args(args) 97 + if #args == 0 then 98 + return c.gotag.default_tag 103 99 end 100 + return table.concat(args, ",") 101 + end 104 102 105 - local cmd_args = { "-add-tags" } 106 - for _, v in ipairs(arg) do 107 - table.insert(cmd_args, v) 108 - end 103 + -- Adds tags to a struct under the cursor 104 + -- See |gopher.nvim-struct-tags| 105 + ---@param ... string Tags to add to the struct fields. If not provided, it will use [config.gotag.default_tag] 106 + ---@dochide 107 + function struct_tags.add(...) 108 + local args = { ... } 109 + local fpath = vim.fn.expand "%" 110 + local bufnr = vim.api.nvim_get_current_buf() 109 111 110 - modify(unpack(cmd_args)) 112 + local user_tags = handler_user_args(args) 113 + handle_tags(fpath, bufnr, { "-add-tags", user_tags }) 111 114 end 112 115 113 - -- remove tags to struct under cursor 116 + -- Removes tags from a struct under the cursor 117 + -- See `:h gopher.nvim-struct-tags` 118 + ---@dochide 119 + ---@param ... string Tags to add to the struct fields. If not provided, it will use [config.gotag.default_tag] 114 120 function struct_tags.remove(...) 115 - local arg = { ... } 116 - if #arg == nil or arg == "" then 117 - arg = { "json" } 118 - end 121 + local args = { ... } 122 + local fpath = vim.fn.expand "%" 123 + local bufnr = vim.api.nvim_get_current_buf() 119 124 120 - local cmd_args = { "-remove-tags" } 121 - for _, v in ipairs(arg) do 122 - table.insert(cmd_args, v) 123 - end 125 + local user_tags = handler_user_args(args) 126 + handle_tags(fpath, bufnr, { "-remove-tags", user_tags }) 127 + end 124 128 125 - modify(unpack(cmd_args)) 129 + -- Removes all tags from a struct under the cursor 130 + -- See `:h gopher.nvim-struct-tags` 131 + ---@dochide 132 + function struct_tags.clear() 133 + local fpath = vim.fn.expand "%" 134 + local bufnr = vim.api.nvim_get_current_buf() 135 + handle_tags(fpath, bufnr, { "-clear-tags" }) 126 136 end 127 137 128 138 return struct_tags
+3
nvim.toml
··· 1 1 [vim] 2 2 any = true 3 3 4 + [MiniTest] 5 + any = true 6 + 4 7 [describe] 5 8 any = true 6 9 [[describe.args]]
+13
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 + "dependencies": { 11 + "https://github.com/nvim-treesitter/nvim-treesitter": "*" 12 + } 13 + }
+87
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 + ---@private 15 + local function cmd(name, fn, nargs) 16 + nargs = nargs or 0 17 + vim.api.nvim_create_user_command(name, fn, { nargs = nargs }) 18 + end 19 + 20 + cmd("GopherLog", function() 21 + vim.cmd("tabnew " .. require("gopher._utils.log").get_outfile()) 22 + end) 23 + 24 + cmd("GoIfErr", function() 25 + require("gopher").iferr() 26 + end) 27 + 28 + cmd("GoCmt", function() 29 + require("gopher").comment() 30 + end) 31 + 32 + cmd("GoImpl", function(args) 33 + require("gopher").impl(unpack(args.fargs)) 34 + end, "*") 35 + 36 + -- :GoInstall 37 + cmd("GoInstallDeps", function() 38 + require("gopher").install_deps() 39 + end) 40 + 41 + cmd("GoInstallDepsSync", function() 42 + require("gopher").install_deps { sync = true } 43 + end) 44 + 45 + -- :GoTag 46 + cmd("GoTagAdd", function(opts) 47 + require("gopher").tags.add(unpack(opts.fargs)) 48 + end, "*") 49 + 50 + cmd("GoTagRm", function(opts) 51 + require("gopher").tags.rm(unpack(opts.fargs)) 52 + end, "*") 53 + 54 + cmd("GoTagClear", function() 55 + require("gopher").tags.clear() 56 + end) 57 + 58 + -- :GoTest 59 + cmd("GoTestAdd", function() 60 + require("gopher").test.add() 61 + end) 62 + 63 + cmd("GoTestsAll", function() 64 + require("gopher").test.all() 65 + end) 66 + 67 + cmd("GoTestsExp", function() 68 + require("gopher").test.exported() 69 + end) 70 + 71 + -- :Go 72 + cmd("GoMod", function(opts) 73 + require("gopher").mod(opts.fargs) 74 + end, "*") 75 + 76 + cmd("GoGet", function(opts) 77 + vim.print(opts) 78 + require("gopher").get(opts.fargs) 79 + end, "*") 80 + 81 + cmd("GoWork", function(opts) 82 + require("gopher").get(opts.fargs) 83 + end, "*") 84 + 85 + cmd("GoGenerate", function(opts) 86 + require("gopher").generate(opts.fargs or "") 87 + end, "?")
-14
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! GoTestAdd :lua require"gopher".test.add() 4 - command! GoTestsAll :lua require"gopher".test.all() 5 - command! GoTestsExp :lua require"gopher".test.exported() 6 - command! -nargs=* GoMod :lua require"gopher".mod(<f-args>) 7 - command! -nargs=* GoGet :lua require"gopher".get(<f-args>) 8 - command! -nargs=* GoWork :lua require"gopher".work(<f-args>) 9 - command! -nargs=* GoImpl :lua require"gopher".impl(<f-args>) 10 - command! -nargs=* GoGenerate :lua require"gopher".generate(<f-args>) 11 - command! GoCmt :lua require"gopher".comment() 12 - command! GoIfErr :lua require"gopher".iferr() 13 - command! GoInstallDeps :lua require"gopher".install_deps() 14 - command! GopherLog :lua vim.cmd("tabnew " .. require("gopher._utils.log").get_outfile())
+5 -1
scripts/docgen.lua
··· 10 10 local files = { 11 11 "lua/gopher/init.lua", 12 12 "lua/gopher/config.lua", 13 + "plugin/gopher.lua", 13 14 "lua/gopher/struct_tags.lua", 14 15 "lua/gopher/impl.lua", 15 16 "lua/gopher/gotests.lua", 16 17 "lua/gopher/iferr.lua", 17 18 "lua/gopher/comment.lua", 18 - "lua/gopher/dap.lua", 19 19 } 20 20 21 21 minidoc.setup() ··· 28 28 table.remove(lines, 1) 29 29 30 30 return lines 31 + end 32 + 33 + hooks.sections["@dochide"] = function(s) 34 + s.parent:clear_lines() 31 35 end 32 36 33 37 MiniDoc.generate(files, "doc/gopher.nvim.txt", { hooks = hooks })
+43 -14
scripts/minimal_init.lua
··· 6 6 local function install_plug(plugin) 7 7 local name = plugin:match ".*/(.*)" 8 8 local package_root = root ".tests/site/pack/deps/start/" 9 - if not vim.loop.fs_stat(package_root .. name) then 9 + if not vim.uv.fs_stat(package_root .. name) then 10 10 print("Installing " .. plugin) 11 - vim.fn.mkdir(package_root, "p") 12 - vim.fn.system { 13 - "git", 14 - "clone", 15 - "--depth=1", 16 - "https://github.com/" .. plugin .. ".git", 17 - package_root .. "/" .. name, 18 - } 11 + vim 12 + .system({ 13 + "git", 14 + "clone", 15 + "--depth=1", 16 + "https://github.com/" .. plugin .. ".git", 17 + package_root .. "/" .. name, 18 + }) 19 + :wait() 19 20 end 20 21 end 21 22 22 - vim.cmd [[set runtimepath=$VIMRUNTIME]] 23 - vim.opt.runtimepath:append(root()) 24 - vim.opt.packpath = { root ".tests/site" } 25 - vim.notify = print 26 - 27 23 install_plug "nvim-lua/plenary.nvim" 28 24 install_plug "nvim-treesitter/nvim-treesitter" 29 25 install_plug "echasnovski/mini.doc" -- used for docs generation 26 + install_plug "echasnovski/mini.test" 30 27 31 28 vim.env.XDG_CONFIG_HOME = root ".tests/config" 32 29 vim.env.XDG_DATA_HOME = root ".tests/data" 33 30 vim.env.XDG_STATE_HOME = root ".tests/state" 34 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.notify = vim.print 36 + 37 + -- install go treesitter parse 38 + require("nvim-treesitter.install").ensure_installed_sync "go" 39 + 40 + require("gopher").setup { 41 + log_level = vim.log.levels.OFF, 42 + timeout = 4000, 43 + } 44 + 45 + -- setup mini.test only when running headless nvim 46 + if #vim.api.nvim_list_uis() == 0 then 47 + require("mini.test").setup { 48 + collect = { 49 + find_files = function() 50 + return vim.fn.globpath("spec", "**/*_test.lua", true, true) 51 + end, 52 + }, 53 + } 54 + end 55 + 56 + -- needed for tests, i dont know the reason why, but on start 57 + -- vim is not able to use treesitter for go by default 58 + vim.api.nvim_create_autocmd("FileType", { 59 + pattern = "go", 60 + callback = function(args) 61 + vim.treesitter.start(args.buf, "go") 62 + end, 63 + })
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{}
+4
spec/fixtures/comment/interface_output.go
··· 1 + package main 2 + 3 + // Testinger 4 + type Testinger interface{}
+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
+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{}
+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 + }
+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 + }
+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 + )
+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 + }
+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 + }
+37
spec/integration/comment_test.lua
··· 1 + local t = require "spec.testutils" 2 + local child, T = 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 + T["comment"]["should add comment to package"] = function() 14 + do_the_test("package", { 1, 1 }) 15 + end 16 + 17 + T["comment"]["should add comment to struct"] = function() 18 + do_the_test("struct", { 4, 1 }) 19 + end 20 + 21 + T["comment"]["should add comment to function"] = function() 22 + do_the_test("func", { 3, 1 }) 23 + end 24 + 25 + T["comment"]["should add comment to method"] = function() 26 + do_the_test("method", { 5, 1 }) 27 + end 28 + 29 + T["comment"]["should add comment to interface"] = function() 30 + do_the_test("interface", { 3, 6 }) 31 + end 32 + 33 + T["comment"]["otherwise should add // above cursor"] = function() 34 + do_the_test("empty", { 1, 1 }) 35 + end 36 + 37 + return T
+29
spec/integration/gotests_test.lua
··· 1 + local t = require "spec.testutils" 2 + local child, T = 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 + T["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 + T["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
+27
spec/integration/iferr_test.lua
··· 1 + local t = require "spec.testutils" 2 + local child, T = t.setup "iferr" 3 + 4 + T["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 + T["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 + local rs = t.setup_test("iferr/message", child, { 6, 2 }) 20 + child.cmd "GoIfErr" 21 + child.cmd "write" 22 + 23 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 24 + t.cleanup(rs) 25 + end 26 + 27 + return T
+35
spec/integration/impl_test.lua
··· 1 + local t = require "spec.testutils" 2 + local child, T = t.setup "impl" 3 + 4 + T["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 + T["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 + T["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
+81
spec/integration/struct_tags_test.lua
··· 1 + local t = require "spec.testutils" 2 + local child, T = t.setup "struct_tags" 3 + 4 + T["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 + T["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 + T["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 + T["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 + T["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 + T["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 + T["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 + return T
+102
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 name string 10 + ---@return MiniTest.child, table 11 + function testutils.setup(name) 12 + local child = MiniTest.new_child_neovim() 13 + local T = MiniTest.new_set { 14 + hooks = { 15 + post_once = child.stop, 16 + pre_case = function() 17 + child.restart { "-u", testutils.mininit_path } 18 + end, 19 + }, 20 + } 21 + 22 + T[name] = MiniTest.new_set {} 23 + return child, T 24 + end 25 + 26 + ---@generic T 27 + ---@param a T 28 + ---@param b T 29 + ---@return boolean 30 + function testutils.eq(a, b) 31 + return MiniTest.expect.equality(a, b) 32 + end 33 + 34 + ---@return string 35 + function testutils.tmpfile() 36 + return vim.fn.tempname() .. ".go" 37 + end 38 + 39 + ---@param path string 40 + ---@return string 41 + function testutils.readfile(path) 42 + return vim.fn.join(vim.fn.readfile(path), "\n") 43 + end 44 + 45 + ---@param fpath string 46 + ---@param contents string 47 + function testutils.writefile(fpath, contents) 48 + vim.fn.writefile(vim.split(contents, "\n"), fpath) 49 + end 50 + 51 + ---@param fpath string 52 + function testutils.deletefile(fpath) 53 + vim.fn.delete(fpath) 54 + end 55 + 56 + ---@class gopher.TestUtilsFixtures 57 + ---@field input string 58 + ---@field output string 59 + 60 + ---@param fixture string 61 + ---@return gopher.TestUtilsFixtures 62 + function testutils.get_fixtures(fixture) 63 + return { 64 + input = testutils.readfile(vim.fs.joinpath(testutils.fixtures_dir, fixture) .. "_input.go"), 65 + output = testutils.readfile(vim.fs.joinpath(testutils.fixtures_dir, fixture) .. "_output.go"), 66 + } 67 + end 68 + 69 + ---@class gopher.TestUtilsSetup 70 + ---@field tmp string 71 + ---@field fixtures gopher.TestUtilsFixtures 72 + ---@field bufnr number 73 + 74 + ---@param fixture string 75 + ---@param child MiniTest.child 76 + ---@param pos? number[] 77 + ---@return gopher.TestUtilsSetup 78 + function testutils.setup_test(fixture, child, pos) 79 + local tmp = testutils.tmpfile() 80 + local fixtures = testutils.get_fixtures(fixture) 81 + 82 + testutils.writefile(tmp, fixtures.input) 83 + child.cmd("silent edit " .. tmp) 84 + 85 + local bufnr = child.fn.bufnr(tmp) 86 + if pos then 87 + child.fn.setpos(".", { bufnr, unpack(pos) }) 88 + end 89 + 90 + return { 91 + tmp = tmp, 92 + bufnr = bufnr, 93 + fixtures = fixtures, 94 + } 95 + end 96 + 97 + ---@param inp gopher.TestUtilsSetup 98 + function testutils.cleanup(inp) 99 + testutils.deletefile(inp.tmp) 100 + end 101 + 102 + return testutils
+25
spec/unit/utils_test.lua
··· 1 + local t = require "spec.testutils" 2 + local _, T = t.setup "utils" 3 + 4 + T["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 + T["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 + T["utils"]["should .trimend()"] = function() 21 + local u = require "gopher._utils" 22 + t.eq(u.trimend " hi ", " hi") 23 + end 24 + 25 + return T
-29
spec/units/config_spec.lua
··· 1 - describe("gopher.config", function() 2 - it(".setup() should provide default when .setup() is not called", function() 3 - local c = require "gopher.config" 4 - 5 - assert.are.same(c.commands.go, "go") 6 - assert.are.same(c.commands.gomodifytags, "gomodifytags") 7 - assert.are.same(c.commands.gotests, "gotests") 8 - assert.are.same(c.commands.impl, "impl") 9 - assert.are.same(c.commands.iferr, "iferr") 10 - assert.are.same(c.commands.dlv, "dlv") 11 - end) 12 - 13 - it(".setup() should change options on users config", function() 14 - local c = require "gopher.config" 15 - c.setup { 16 - commands = { 17 - go = "go1.420", 18 - gomodifytags = "iDontUseRustBtw", 19 - }, 20 - } 21 - 22 - assert.are.same(c.commands.go, "go1.420") 23 - assert.are.same(c.commands.gomodifytags, "iDontUseRustBtw") 24 - assert.are.same(c.commands.gotests, "gotests") 25 - assert.are.same(c.commands.impl, "impl") 26 - assert.are.same(c.commands.iferr, "iferr") 27 - assert.are.same(c.commands.dlv, "dlv") 28 - end) 29 - end)
-15
spec/units/utils_spec.lua
··· 1 - describe("gopher._utils", function() 2 - local u = require "gopher._utils" 3 - 4 - describe(".sreq()", function() 5 - it("can require existing module", function() 6 - assert.are.same(require "gopher", u.sreq "gopher") 7 - end) 8 - 9 - it("cannot require non-existing module", function() 10 - assert.has.errors(function() 11 - u.sreq "iDontExistBtw" 12 - end) 13 - end) 14 - end) 15 - end)