├── .github ├── linters │ └── .luacheckrc ├── stale.yml └── workflows │ └── lint.yml ├── .gitignore ├── .luacheckrc ├── .stylua.toml ├── LICENSE ├── README.md ├── doc └── nvim-dap-go.txt ├── images ├── image1.png ├── image2.png └── image3.png ├── lua ├── dap-go-ts.lua └── dap-go.lua └── tests ├── go.mod └── subtest_bar_test.go /.github/linters/.luacheckrc: -------------------------------------------------------------------------------- 1 | ../../.luacheckrc -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 180 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 30 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | pull_request: ~ 4 | push: 5 | branches: 6 | - master 7 | jobs: 8 | stylua: 9 | name: Stylua 10 | runs-on: ubuntu-20.04 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: JohnnyMorganz/stylua-action@v2 14 | with: 15 | token: ${{ secrets.GITHUB_TOKEN }} 16 | # CLI arguments 17 | args: --color always --check lua/ 18 | 19 | super-linter: 20 | name: Super Linter 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout Code 24 | uses: actions/checkout@v2 25 | - name: Lint Code Base 26 | uses: github/super-linter/slim@v4 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | VALIDATE_JSCPD: false 30 | VALIDATE_PYTHON_BLACK: false 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | .luarc.json 3 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | globals = { 2 | "vim", 3 | } 4 | read_globals = { 5 | "describe", 6 | "it", 7 | "before_each", 8 | "after_each", 9 | "assert" 10 | } 11 | -------------------------------------------------------------------------------- /.stylua.toml: -------------------------------------------------------------------------------- 1 | column_width = 120 2 | line_endings = "Unix" 3 | indent_type = "Spaces" 4 | indent_width = 2 5 | quote_style = "AutoPreferDouble" 6 | call_parentheses = "Always" 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Leonardo Luz Almeida 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nvim-dap-go 2 | 3 | An extension for [nvim-dap][1] providing configurations for launching go debugger (delve) and debugging individual tests. 4 | 5 | ## Features 6 | 7 | - Auto launch Delve. No configuration needed. You just have to have `dlv` in your path. 8 | - Run just the closest test from the cursor in debug mode (uses treesitter). See [debugging individual tests](#debugging-individual-tests) section below for more details. 9 | - Configuration to attach nvim-dap and Delve into a running process and start a debug session. 10 | - Configuration to start a debug session in the main function. 11 | - Configuration to run tests in a debug session. 12 | - Final Delve configuration is resolved when a debug session starts. This allows to use different addresses and ports for each project or launch configs in a project. 13 | 14 | ## Pre-reqs 15 | 16 | - Neovim >= 0.9.0 17 | - [nvim-dap][1] 18 | - [delve][2] >= 1.7.0 19 | 20 | This plugin extension make usage of treesitter to find the nearest test to debug. 21 | Make sure you have the Go treesitter parser installed. 22 | If using [nvim-treesitter][3] plugin you can install with `:TSInstall go`. 23 | 24 | ## Installation 25 | 26 | - Install like any other neovim plugin: 27 | - If using [vim-plug][4]: `Plug 'leoluz/nvim-dap-go'` 28 | - If using [packer.nvim][5]: `use 'leoluz/nvim-dap-go'` 29 | 30 | ## Usage 31 | 32 | ### Register the plugin 33 | 34 | Call the setup function in your `init.vim` to register the go adapter and the configurations to debug go tests: 35 | 36 | ```vimL 37 | lua require('dap-go').setup() 38 | ``` 39 | 40 | ### Configuring 41 | 42 | It is possible to customize nvim-dap-go by passing a config table in the setup function. 43 | 44 | The example below shows all the possible configurations: 45 | 46 | ```lua 47 | lua require('dap-go').setup { 48 | -- Additional dap configurations can be added. 49 | -- dap_configurations accepts a list of tables where each entry 50 | -- represents a dap configuration. For more details do: 51 | -- :help dap-configuration 52 | dap_configurations = { 53 | { 54 | -- Must be "go" or it will be ignored by the plugin 55 | type = "go", 56 | name = "Attach remote", 57 | mode = "remote", 58 | request = "attach", 59 | }, 60 | }, 61 | -- delve configurations 62 | delve = { 63 | -- the path to the executable dlv which will be used for debugging. 64 | -- by default, this is the "dlv" executable on your PATH. 65 | path = "dlv", 66 | -- time to wait for delve to initialize the debug session. 67 | -- default to 20 seconds 68 | initialize_timeout_sec = 20, 69 | -- a string that defines the port to start delve debugger. 70 | -- default to string "${port}" which instructs nvim-dap 71 | -- to start the process in a random available port. 72 | -- if you set a port in your debug configuration, its value will be 73 | -- assigned dynamically. 74 | port = "${port}", 75 | -- additional args to pass to dlv 76 | args = {}, 77 | -- the build flags that are passed to delve. 78 | -- defaults to empty string, but can be used to provide flags 79 | -- such as "-tags=unit" to make sure the test suite is 80 | -- compiled during debugging, for example. 81 | -- passing build flags using args is ineffective, as those are 82 | -- ignored by delve in dap mode. 83 | -- avaliable ui interactive function to prompt for arguments get_arguments 84 | build_flags = {}, 85 | -- whether the dlv process to be created detached or not. there is 86 | -- an issue on delve versions < 1.24.0 for Windows where this needs to be 87 | -- set to false, otherwise the dlv server creation will fail. 88 | -- avaliable ui interactive function to prompt for build flags: get_build_flags 89 | detached = vim.fn.has("win32") == 0, 90 | -- the current working directory to run dlv from, if other than 91 | -- the current working directory. 92 | cwd = nil, 93 | }, 94 | -- options related to running closest test 95 | tests = { 96 | -- enables verbosity when running the test. 97 | verbose = false, 98 | }, 99 | } 100 | ``` 101 | 102 | ### Use nvim-dap as usual 103 | 104 | - Call `:lua require('dap').continue()` to start debugging. 105 | - All pre-configured debuggers will be displayed for you to choose from. 106 | - See `:help dap-mappings` and `:help dap-api`. 107 | 108 | ### Debugging individual tests 109 | 110 | To debug the closest method above the cursor use you can run: 111 | 112 | - `:lua require('dap-go').debug_test()` 113 | 114 | Once a test was run, you can simply run it again from anywhere: 115 | 116 | - `:lua require('dap-go').debug_last_test()` 117 | 118 | It is better to define a mapping to invoke this command. See the mapping section below. 119 | 120 | ### Debugging with command-line arguments 121 | 122 | 1. Select the option `Debug (Arguments)` 123 | 1. Enter each argument separated by a space (i.e. `option1 option2 option3`) 124 | 1. Press enter 125 | 126 | ![Start Debug Session with Arguments](./images/image1.png "Start Debug Session with Arguments") 127 | ![Enter Arguments](./images/image2.png "Enter Arguments") 128 | ![Begin Debugging](./images/image3.png "Being Debugging") 129 | 130 | ### Debugging with build flags 131 | 132 | 1. Register a new option to debug with build flags: 133 | 134 | ```lua 135 | require('dap-go').setup ({ 136 | dap_configurations = { 137 | { 138 | type = "go", 139 | name = "Debug (Build Flags)", 140 | request = "launch", 141 | program = "${file}", 142 | buildFlags = require("dap-go").get_build_flags, 143 | }, 144 | }, 145 | }) 146 | ``` 147 | 148 | 2. To prompt for both build flags and arguments register the following: 149 | 150 | ```lua 151 | require("dap-go").setup({ 152 | dap_configurations = { 153 | { 154 | type = "go", 155 | name = "Debug (Build Flags & Arguments)", 156 | request = "launch", 157 | program = "${file}", 158 | args = require("dap-go").get_arguments, 159 | buildFlags = require("dap-go").get_build_flags, 160 | }, 161 | } 162 | }) 163 | ``` 164 | 165 | 3. To create a custom debugging configuration that requires an interactive prompt the following functions can be 166 | attached to the args and buildFlags fields of dap_configurations. 167 | - `require('dap-go').get_arguments` 168 | - `require('dap-go').get_buid_flags` 169 | 170 | ### Debugging with dlv in headless mode 171 | 172 | 1. Register a new option to attach to a remote debugger: 173 | 174 | ```lua 175 | lua require('dap-go').setup { 176 | dap_configurations = { 177 | { 178 | type = "go", 179 | name = "Attach remote", 180 | mode = "remote", 181 | request = "attach", 182 | }, 183 | }, 184 | } 185 | ``` 186 | 187 | 1. Start `dlv` in headless mode. You can specify subcommands and flags after `--`, e.g., 188 | 189 | ```sh 190 | dlv debug -l 127.0.0.1:38697 --headless ./main.go -- subcommand --myflag=xyz 191 | ``` 192 | 193 | 1. Call `:lua require('dap').continue()` to start debugging. 194 | 1. Select the new registered option `Attach remote`. 195 | 196 | ## Mappings 197 | 198 | ```vimL 199 | nmap td :lua require('dap-go').debug_test() 200 | ``` 201 | 202 | ## VSCode launch config 203 | 204 | Defining the Go debug configurations for all your projects inside your Neovim configuration can be cumbersome and quite strict. 205 | For more flexibility, `nvim-dap` supports the use of the VSCode launch configurations. 206 | 207 | That allows for example to set the Delve port dynamically when you run a debug session. If you create this file in your project (`[root_project]/.vscode/launch.json`): 208 | 209 | ```json 210 | { 211 | "version": "0.2.0", 212 | "configurations": [ 213 | { 214 | "name": "Remote debug API server", 215 | "type": "go", 216 | "request": "attach", 217 | "mode": "remote", 218 | "port": 4444, 219 | "host": "127.0.0.1", 220 | "substitutePath": [ 221 | { 222 | "from": "${workspaceFolder}", "to": "/usr/src/app" 223 | } 224 | ] 225 | } 226 | ] 227 | } 228 | ``` 229 | 230 | A debug session `Remote debug API server` will appear in the choices, and the Delve port will be dynamically set to `4444`. 231 | The current version of nvim-dap always loads the file if it exists. 232 | 233 | Please see `:h dap-launch.json` for more information. 234 | 235 | ## Acknowledgement 236 | 237 | Thanks to the [nvim-dap-python][6] for the inspiration. 238 | 239 | [1]: https://github.com/mfussenegger/nvim-dap 240 | [2]: https://github.com/go-delve/delve 241 | [3]: https://github.com/nvim-treesitter/nvim-treesitter 242 | [4]: https://github.com/junegunn/vim-plug 243 | [5]: https://github.com/wbthomason/packer.nvim 244 | [6]: https://github.com/mfussenegger/nvim-dap-python 245 | -------------------------------------------------------------------------------- /doc/nvim-dap-go.txt: -------------------------------------------------------------------------------- 1 | ======================================================================== 2 | INTRODUCTION *dap-go* 3 | 4 | nvim-dap-go is an extension for nvim-dap (see |dap.txt|) providing 5 | configurations for launching go debugger (delve) and debugging 6 | individual tests. As such, it requires nvim-dap go to be installed. 7 | For more details see 8 | https://github.com/mfussenegger/nvim-dap#installation 9 | 10 | ======================================================================== 11 | CONTENTS *dap-go-toc* 12 | 13 | 1. Features .............................. |dap-go-features| 14 | 2. Configuration ......................... |dap-go-configuration| 15 | 3. Usage ................................. |dap-go-usage| 16 | 4. Debugging Individual Tests ............ |dap-go-debug-test| 17 | 5. Debugging With Command-Line Arguments . |dap-go-debug-cli-args| 18 | 6. Debugging With Build Flags ............ |dap-go-debug-cli-args| 19 | 7. Debugging With dlv in Headless Mode ... |dap-go-debug-headless| 20 | 8. VSCode launch config .................. |dap-go-vscode-launch| 21 | 9. Mappings .............................. |dap-go-mappings| 22 | 23 | ======================================================================== 24 | FEATURES *dap-go-features* 25 | 26 | - Auto launch Delve. No configuration needed. You just have to have 27 | `dlv` in your path. 28 | 29 | - Run just the closest test from the cursor in debug mode (uses 30 | |treesitter|). See [debugging individual 31 | tests](#debugging-individual-tests) section bellow for more details. 32 | 33 | - Configuration to attach nvim-dap and Delve into a running process 34 | and start a debug session. 35 | 36 | - Configuration to start a debug session in the main function. 37 | 38 | - Configuration to run tests in a debug session. 39 | 40 | - Final Delve configuration is resolved when a debug session starts. 41 | This allows to use different addresses and ports for each project or 42 | launch configs in a project. 43 | 44 | This plugin makes usage of treesitter to find the nearest test to 45 | debug. Make sure you have the Go treesitter parser installed. If using 46 | |nvim-treesitter| plugin you can install with `:TSInstall go`. 47 | 48 | ======================================================================== 49 | CONFIGURATION *dap-go-configuration* 50 | 51 | Register the plugin by calling the setup function in your `init.lua`: 52 | 53 | >lua 54 | require('dap-go').setup() 55 | < 56 | 57 | This will apply all default configurations which is all you need for 58 | normal use-cases. It is possible to customize nvim-dap-go by passing a 59 | config table in the setup function. 60 | 61 | The example bellow shows all the possible configurations: 62 | 63 | >lua 64 | require('dap-go').setup { 65 | -- Additional dap configurations can be added. 66 | -- dap_configurations accepts a list of tables where each entry 67 | -- represents a dap configuration. For more details see: 68 | -- |dap-configuration| 69 | dap_configurations = { 70 | { 71 | -- Must be "go" or it will be ignored by the plugin 72 | type = "go", 73 | name = "Attach remote", 74 | mode = "remote", 75 | request = "attach", 76 | }, 77 | }, 78 | -- delve configurations 79 | delve = { 80 | -- the path to the executable dlv which will be used for debugging. 81 | -- by default, this is the "dlv" executable on your PATH. 82 | path = "dlv", 83 | -- time to wait for delve to initialize the debug session. 84 | -- default to 20 seconds 85 | initialize_timeout_sec = 20, 86 | -- a string that defines the port to start delve debugger. 87 | -- default to string "${port}" which instructs nvim-dap 88 | -- to start the process in a random available port. 89 | -- if you set a port in your debug configuration, its value will be 90 | -- assigned dynamically. 91 | port = "${port}", 92 | -- additional args to pass to dlv 93 | args = {}, 94 | -- the build flags that are passed to delve. 95 | -- defaults to empty string, but can be used to provide flags 96 | -- such as "-tags=unit" to make sure the test suite is 97 | -- compiled during debugging, for example. 98 | -- passing build flags using args is ineffective, as those are 99 | -- ignored by delve in dap mode. 100 | build_flags = "", 101 | -- whether the dlv process to be created detached or not. there is 102 | -- an issue on delve versions < 1.24.0 for Windows where this needs to be 103 | -- set to false, otherwise the dlv server creation will fail. 104 | detached = vim.fn.has("win32") == 0, 105 | }, 106 | -- options related to running closest test 107 | tests = { 108 | -- enables verbosity when running the test. 109 | verbose = false, 110 | }, 111 | } 112 | < 113 | 114 | ======================================================================== 115 | USAGE *dap-go-usage* 116 | 117 | Once the plugin is registered, use nvim-dap as usual: 118 | 119 | - Call `:lua require('dap').continue()` to start debugging. 120 | - All pre-configured debuggers will be displayed for you to choose 121 | from. 122 | - See |dap-mappings| and |dap-api|. 123 | 124 | ----------------------------------------------------------------------- 125 | Debugging Individual Tests *dap-go-debug-test* 126 | 127 | To debug the closest method above the cursor use you can run: 128 | > 129 | :lua require('dap-go').debug_test()` 130 | < 131 | 132 | The |dap-configuration| in use can be customized by passing an optional 133 | table argument to `debug-test`. For example, you can override default 134 | build flags as follows: 135 | >lua 136 | require("dap-go").debug_test({ 137 | buildFlags = "-tags=integration", 138 | }) 139 | < 140 | 141 | Once a test runs, you can simply run it again from anywhere: 142 | > 143 | :lua require('dap-go').debug_last_test()` 144 | < 145 | 146 | It is better to define mappings to invoke these commands. See the 147 | |dap-go-mappings| section bellow. 148 | 149 | ----------------------------------------------------------------------- 150 | Debugging With Command-Line Arguments *dap-go-debug-cli-args* 151 | 152 | 1. Select the option `Debug (Arguments)`. 153 | 2. Enter each argument separated by a space (i.e. `option1 option2 154 | option3`). 155 | 3. Press enter. 156 | 157 | ----------------------------------------------------------------------- 158 | Debugging With Build Flags *dap-go-debug-build-flags* 159 | 160 | 1. Register a new option to debug with build flags: 161 | >lua 162 | require('dap-go').setup { 163 | dap_configurations = { 164 | { 165 | type = "go", 166 | name = "Debug (Build Flags)", 167 | request = "launch", 168 | program = "${file}", 169 | buildFlags = require("dap-go").get_build_flags, 170 | }, 171 | }, 172 | }) 173 | < 174 | 175 | 2. To prompt for both build flags and arguments register the 176 | following: 177 | >lua 178 | require("dap-go").setup({ 179 | dap_configurations = { 180 | { 181 | type = "go", 182 | name = "Debug (Build Flags & Arguments)", 183 | request = "launch", 184 | program = "${file}", 185 | args = require("dap-go").get_arguments, 186 | buildFlags = require("dap-go").get_build_flags, 187 | }, 188 | } 189 | }) 190 | < 191 | 192 | 3. To create a custom debugging configuration that requires an 193 | interactive prompt the following functions can be attached to 194 | the args and buildFlags fields of dap_configurations. 195 | 196 | `require('dap-go').get_arguments` 197 | `require('dap-go').get_buid_flags` 198 | 199 | ----------------------------------------------------------------------- 200 | Debugging With dlv in Headless Mode *dap-go-debug-headless* 201 | 202 | 1. Register a new option to attach to a remote debugger: 203 | >lua 204 | require('dap-go').setup { 205 | dap_configurations = { 206 | { 207 | type = "go", 208 | name = "Attach remote", 209 | mode = "remote", 210 | request = "attach", 211 | }, 212 | }, 213 | } 214 | < 215 | 216 | 2. Start `dlv` in headless mode. You can specify subcommands and 217 | flags after `--`, e.g., 218 | 219 | >sh 220 | dlv debug -l 127.0.0.1:38697 --headless ./main.go -- subcommand --myflag=xyz 221 | < 222 | 223 | 3. Call `:lua require('dap').continue()` to start debugging. 224 | 4. Select the new registered option `Attach remote`. 225 | 226 | ----------------------------------------------------------------------- 227 | VSCode launch config *dap-go-vscode-launch 228 | 229 | 1. Create in your Go project a VSCode launch config file (by 230 | default its path must be `.vscode/launch.json` relative to your 231 | project's root directory): 232 | >json 233 | { 234 | "version": "0.2.0", 235 | "configurations": [ 236 | { 237 | "name": "Remote debug API server", 238 | "type": "go", 239 | "request": "attach", 240 | "mode": "remote", 241 | "port": 4444, 242 | "host": "127.0.0.1", 243 | "substitutePath": [ 244 | { 245 | "from": "${workspaceFolder}", "to": "/usr/src/app" 246 | } 247 | ] 248 | } 249 | ] 250 | } 251 | < 252 | 253 | 2. A debug session `Remote debug API server` will appear in the choices, 254 | and the Delve port will be dynamically set to `4444`. 255 | 256 | Please see `:h dap-launch.json` for more information. 257 | 258 | ======================================================================== 259 | MAPPINGS *dap-go-mappings* 260 | 261 | nvim-dap-go doesn't provide any pre-configured keymaps. It does 262 | provide lua functions that you can easly use to define your own 263 | mappings. You can do this by adding the lines bellow to your 264 | `init.lua`: 265 | 266 | >lua 267 | local dapgo = require('dap-go') 268 | vim.keymap.set("n", "dt", dapgo.debug_test) 269 | vim.keymap.set("n", "dl", dapgo.debug_last_test) 270 | < 271 | vim:tw=78:et:ft=help:norl: 272 | -------------------------------------------------------------------------------- /images/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leoluz/nvim-dap-go/b4421153ead5d726603b02743ea40cf26a51ed5f/images/image1.png -------------------------------------------------------------------------------- /images/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leoluz/nvim-dap-go/b4421153ead5d726603b02743ea40cf26a51ed5f/images/image2.png -------------------------------------------------------------------------------- /images/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leoluz/nvim-dap-go/b4421153ead5d726603b02743ea40cf26a51ed5f/images/image3.png -------------------------------------------------------------------------------- /lua/dap-go-ts.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local tests_query = [[ 4 | (function_declaration 5 | name: (identifier) @testname 6 | parameters: (parameter_list 7 | . (parameter_declaration 8 | type: (pointer_type) @type) .) 9 | (#match? @type "*testing.(T|M)") 10 | (#match? @testname "^Test.+$")) @parent 11 | ]] 12 | 13 | local subtests_query = [[ 14 | (call_expression 15 | function: (selector_expression 16 | operand: (identifier) 17 | field: (field_identifier) @run) 18 | arguments: (argument_list 19 | (interpreted_string_literal) @testname 20 | [ 21 | (func_literal) 22 | (identifier) 23 | ]) 24 | (#eq? @run "Run")) @parent 25 | ]] 26 | 27 | local function format_subtest(testcase, test_tree) 28 | local parent 29 | if testcase.parent then 30 | for _, curr in pairs(test_tree) do 31 | if curr.name == testcase.parent then 32 | parent = curr 33 | break 34 | end 35 | end 36 | return string.format("%s/%s", format_subtest(parent, test_tree), testcase.name) 37 | else 38 | return testcase.name 39 | end 40 | end 41 | 42 | local function get_closest_above_cursor(test_tree) 43 | local result 44 | for _, curr in pairs(test_tree) do 45 | if not result then 46 | result = curr 47 | else 48 | local node_row1, _, _, _ = curr.node:range() 49 | local result_row1, _, _, _ = result.node:range() 50 | if node_row1 > result_row1 then 51 | result = curr 52 | end 53 | end 54 | end 55 | if result then 56 | return format_subtest(result, test_tree) 57 | end 58 | return nil 59 | end 60 | 61 | local function is_parent(dest, source) 62 | if not (dest and source) then 63 | return false 64 | end 65 | if dest == source then 66 | return false 67 | end 68 | 69 | local current = source 70 | while current ~= nil do 71 | if current == dest then 72 | return true 73 | end 74 | 75 | current = current:parent() 76 | end 77 | 78 | return false 79 | end 80 | 81 | local function get_closest_test() 82 | local stop_row = vim.api.nvim_win_get_cursor(0)[1] 83 | local ft = vim.api.nvim_buf_get_option(0, "filetype") 84 | assert(ft == "go", "can only find test in go files, not " .. ft) 85 | local parser = vim.treesitter.get_parser(0) 86 | local root = (parser:parse()[1]):root() 87 | 88 | local test_tree = {} 89 | 90 | local test_query = vim.treesitter.query.parse(ft, tests_query) 91 | assert(test_query, "could not parse test query") 92 | for _, match, _ in test_query:iter_matches(root, 0, 0, stop_row, { all = true }) do 93 | local test_match = {} 94 | for id, nodes in pairs(match) do 95 | for _, node in ipairs(nodes) do 96 | local capture = test_query.captures[id] 97 | if capture == "testname" then 98 | local name = vim.treesitter.get_node_text(node, 0) 99 | test_match.name = name 100 | end 101 | if capture == "parent" then 102 | test_match.node = node 103 | end 104 | end 105 | end 106 | table.insert(test_tree, test_match) 107 | end 108 | 109 | local subtest_query = vim.treesitter.query.parse(ft, subtests_query) 110 | assert(subtest_query, "could not parse test query") 111 | for _, match, _ in subtest_query:iter_matches(root, 0, 0, stop_row, { all = true }) do 112 | local test_match = {} 113 | for id, nodes in pairs(match) do 114 | for _, node in ipairs(nodes) do 115 | local capture = subtest_query.captures[id] 116 | if capture == "testname" then 117 | local name = vim.treesitter.get_node_text(node, 0) 118 | test_match.name = string.gsub(string.gsub(name, " ", "_"), '"', "") 119 | end 120 | if capture == "parent" then 121 | test_match.node = node 122 | end 123 | end 124 | end 125 | table.insert(test_tree, test_match) 126 | end 127 | 128 | table.sort(test_tree, function(a, b) 129 | return is_parent(a.node, b.node) 130 | end) 131 | 132 | for _, parent in ipairs(test_tree) do 133 | for _, child in ipairs(test_tree) do 134 | if is_parent(parent.node, child.node) then 135 | child.parent = parent.name 136 | end 137 | end 138 | end 139 | 140 | return get_closest_above_cursor(test_tree) 141 | end 142 | 143 | local function get_package_name() 144 | local test_dir = vim.fn.fnamemodify(vim.fn.expand("%:.:h"), ":r") 145 | return "./" .. test_dir 146 | end 147 | 148 | M.closest_test = function() 149 | local package_name = get_package_name() 150 | local test_case = get_closest_test() 151 | local test_scope 152 | if test_case then 153 | test_scope = "testcase" 154 | else 155 | test_scope = "package" 156 | end 157 | return { 158 | package = package_name, 159 | name = test_case, 160 | scope = test_scope, 161 | } 162 | end 163 | 164 | M.get_root_dir = function() 165 | local id, client = next(vim.lsp.buf_get_clients()) 166 | if id == nil then 167 | error({ error_msg = "lsp client not attached" }) 168 | end 169 | if not client.config.root_dir then 170 | error({ error_msg = "lsp root_dir not defined" }) 171 | end 172 | return client.config.root_dir 173 | end 174 | 175 | return M 176 | -------------------------------------------------------------------------------- /lua/dap-go.lua: -------------------------------------------------------------------------------- 1 | local ts = require("dap-go-ts") 2 | 3 | local M = { 4 | last_testname = "", 5 | last_testpath = "", 6 | test_buildflags = "", 7 | test_verbose = false, 8 | } 9 | 10 | local default_config = { 11 | delve = { 12 | path = "dlv", 13 | initialize_timeout_sec = 20, 14 | port = "${port}", 15 | args = {}, 16 | build_flags = "", 17 | -- Automatically handle the issue on delve Windows versions < 1.24.0 18 | -- where delve needs to be run in attched mode or it will fail (actually crashes). 19 | detached = vim.fn.has("win32") == 0, 20 | output_mode = "remote", 21 | }, 22 | tests = { 23 | verbose = false, 24 | }, 25 | } 26 | 27 | local internal_global_config = {} 28 | 29 | local function load_module(module_name) 30 | local ok, module = pcall(require, module_name) 31 | assert(ok, string.format("dap-go dependency error: %s not installed", module_name)) 32 | return module 33 | end 34 | 35 | local function get_arguments() 36 | return coroutine.create(function(dap_run_co) 37 | local args = {} 38 | vim.ui.input({ prompt = "Args: " }, function(input) 39 | args = vim.split(input or "", " ") 40 | coroutine.resume(dap_run_co, args) 41 | end) 42 | end) 43 | end 44 | 45 | local function get_build_flags(config) 46 | return coroutine.create(function(dap_run_co) 47 | local build_flags = config.build_flags 48 | vim.ui.input({ prompt = "Build Flags: " }, function(input) 49 | build_flags = vim.split(input or "", " ") 50 | coroutine.resume(dap_run_co, build_flags) 51 | end) 52 | end) 53 | end 54 | 55 | local function filtered_pick_process() 56 | local opts = {} 57 | vim.ui.input( 58 | { prompt = "Search by process name (lua pattern), or hit enter to select from the process list: " }, 59 | function(input) 60 | opts["filter"] = input or "" 61 | end 62 | ) 63 | return require("dap.utils").pick_process(opts) 64 | end 65 | 66 | local function setup_delve_adapter(dap, config) 67 | local args = { "dap", "-l", "127.0.0.1:" .. config.delve.port } 68 | vim.list_extend(args, config.delve.args) 69 | 70 | local delve_config = { 71 | type = "server", 72 | port = config.delve.port, 73 | executable = { 74 | command = config.delve.path, 75 | args = args, 76 | detached = config.delve.detached, 77 | cwd = config.delve.cwd, 78 | }, 79 | options = { 80 | initialize_timeout_sec = config.delve.initialize_timeout_sec, 81 | }, 82 | } 83 | 84 | dap.adapters.go = function(callback, client_config) 85 | if client_config.port == nil then 86 | callback(delve_config) 87 | return 88 | end 89 | 90 | local host = client_config.host 91 | if host == nil then 92 | host = "127.0.0.1" 93 | end 94 | 95 | local listener_addr = host .. ":" .. client_config.port 96 | delve_config.port = client_config.port 97 | delve_config.executable.args = { "dap", "-l", listener_addr } 98 | 99 | callback(delve_config) 100 | end 101 | end 102 | 103 | local function setup_go_configuration(dap, configs) 104 | local common_debug_configs = { 105 | { 106 | type = "go", 107 | name = "Debug", 108 | request = "launch", 109 | program = "${file}", 110 | buildFlags = configs.delve.build_flags, 111 | outputMode = configs.delve.output_mode, 112 | }, 113 | { 114 | type = "go", 115 | name = "Debug (Arguments)", 116 | request = "launch", 117 | program = "${file}", 118 | args = get_arguments, 119 | buildFlags = configs.delve.build_flags, 120 | outputMode = configs.delve.output_mode, 121 | }, 122 | { 123 | type = "go", 124 | name = "Debug (Arguments & Build Flags)", 125 | request = "launch", 126 | program = "${file}", 127 | args = get_arguments, 128 | buildFlags = get_build_flags, 129 | outputMode = configs.delve.output_mode, 130 | }, 131 | { 132 | type = "go", 133 | name = "Debug Package", 134 | request = "launch", 135 | program = "${fileDirname}", 136 | buildFlags = configs.delve.build_flags, 137 | outputMode = configs.delve.output_mode, 138 | }, 139 | { 140 | type = "go", 141 | name = "Attach", 142 | mode = "local", 143 | request = "attach", 144 | processId = filtered_pick_process, 145 | buildFlags = configs.delve.build_flags, 146 | }, 147 | { 148 | type = "go", 149 | name = "Debug test", 150 | request = "launch", 151 | mode = "test", 152 | program = "${file}", 153 | buildFlags = configs.delve.build_flags, 154 | outputMode = configs.delve.output_mode, 155 | }, 156 | { 157 | type = "go", 158 | name = "Debug test (go.mod)", 159 | request = "launch", 160 | mode = "test", 161 | program = "./${relativeFileDirname}", 162 | buildFlags = configs.delve.build_flags, 163 | outputMode = configs.delve.output_mode, 164 | }, 165 | } 166 | 167 | if dap.configurations.go == nil then 168 | dap.configurations.go = {} 169 | end 170 | 171 | for _, config in ipairs(common_debug_configs) do 172 | table.insert(dap.configurations.go, config) 173 | end 174 | 175 | if configs == nil or configs.dap_configurations == nil then 176 | return 177 | end 178 | 179 | for _, config in ipairs(configs.dap_configurations) do 180 | if config.type == "go" then 181 | table.insert(dap.configurations.go, config) 182 | end 183 | end 184 | end 185 | 186 | function M.setup(opts) 187 | internal_global_config = vim.tbl_deep_extend("force", default_config, opts or {}) 188 | M.test_buildflags = internal_global_config.delve.build_flags 189 | M.test_verbose = internal_global_config.tests.verbose 190 | 191 | local dap = load_module("dap") 192 | setup_delve_adapter(dap, internal_global_config) 193 | setup_go_configuration(dap, internal_global_config) 194 | end 195 | 196 | local function debug_test(testname, testpath, build_flags, extra_args, custom_config) 197 | local dap = load_module("dap") 198 | 199 | local config = { 200 | type = "go", 201 | name = testname, 202 | request = "launch", 203 | mode = "test", 204 | program = testpath, 205 | args = { "-test.run", "^" .. testname .. "$" }, 206 | buildFlags = build_flags, 207 | outputMode = "remote", 208 | } 209 | config = vim.tbl_deep_extend("force", config, custom_config or {}) 210 | 211 | if not vim.tbl_isempty(extra_args) then 212 | table.move(extra_args, 1, #extra_args, #config.args + 1, config.args) 213 | end 214 | 215 | dap.run(config) 216 | end 217 | 218 | function M.debug_test(custom_config) 219 | local test = ts.closest_test() 220 | 221 | if test.name == "" or test.name == nil then 222 | vim.notify("no test found") 223 | return false 224 | end 225 | 226 | M.last_testname = test.name 227 | M.last_testpath = test.package 228 | 229 | local msg = string.format("starting debug session '%s : %s'...", test.package, test.name) 230 | vim.notify(msg) 231 | 232 | local extra_args = {} 233 | if M.test_verbose then 234 | extra_args = { "-test.v" } 235 | end 236 | 237 | debug_test(test.name, test.package, M.test_buildflags, extra_args, custom_config) 238 | 239 | return true 240 | end 241 | 242 | function M.debug_last_test() 243 | local testname = M.last_testname 244 | local testpath = M.last_testpath 245 | 246 | if testname == "" then 247 | vim.notify("no last run test found") 248 | return false 249 | end 250 | 251 | local msg = string.format("starting debug session '%s : %s'...", testpath, testname) 252 | vim.notify(msg) 253 | 254 | local extra_args = {} 255 | if M.test_verbose then 256 | extra_args = { "-test.v" } 257 | end 258 | 259 | debug_test(testname, testpath, M.test_buildflags, extra_args) 260 | 261 | return true 262 | end 263 | 264 | function M.get_build_flags() 265 | return get_build_flags(internal_global_config) 266 | end 267 | 268 | function M.get_arguments() 269 | return get_arguments() 270 | end 271 | 272 | return M 273 | -------------------------------------------------------------------------------- /tests/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/leoluz/nvim-dap-go 2 | 3 | go 1.22.2 4 | -------------------------------------------------------------------------------- /tests/subtest_bar_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // https://github.com/leoluz/nvim-dap-go/pull/82 8 | func TestWithSubTests(t *testing.T) { 9 | t.Run("subtest with function literal", func(t *testing.T) { t.Fail() }) 10 | myFunc := func(t *testing.T) { t.FailNow() } 11 | t.Run("subtest with identifier", myFunc) 12 | } 13 | --------------------------------------------------------------------------------