Setting up, Running, and Debugging Go Applications with Neovim on WSL
Introduction
As a minimalist at heart, I’ve always been drawn to simplicity - that’s part of what attracted me to Go. Years of skateboarding and wrist injuries, combined with my ADHD-induced tendency to get distracted by complex IDEs, led me to explore Vim motions and Neovim. While my ultimate goal is to run ArchLinux, blast some breakcore, and hack away on Neovim like a true power user, I’m not quite there yet. As a Windows user, I wanted to find a middle ground - a way to use Neovim and Go without completely abandoning my current setup. Enter WSL (Windows Subsystem for Linux). This guide is my first attempt at setting up a development environment that combines the best of both worlds: Neovim and Go on WSL, with the added ability to debug and step through code. Fair warning: this is just the beginning. Expect more blogs as I continue to refine and expand my Neovim setup. For now, let’s dive into setting up WSL, Go, and the Debug Adapter Protocol (DAP) with Kickstart.nvim. We’ll cover everything from WSL installation to configuring Neovim for Go development and debugging.
Step 1: Installing WSL
Open PowerShell as Administrator and run:
1wsl --install
Restart your computer when prompted.
After restart, open Ubuntu from the Start menu to complete the setup.
Step 2: Installing Go
Update your package list:
1sudo apt update
Remove any existing Go installation:
1sudo rm -rf /usr/local/go
Download Go 1.22.7, you can use whatever go version you want here
1wget https://go.dev/dl/go1.22.7.linux-amd64.tar.gz
Extract Go to /usr/local:
1sudo tar -C /usr/local -xzf go1.22.7.linux-amd64.tar.gz
Set up your Go environment:
1echo 'export GOROOT=/usr/local/go' >> ~/.bashrc 2echo 'export PATH=$GOROOT/bin:$PATH' >> ~/.bashrc 3source ~/.bashrc
Verify the installation:
1go version
Step 3: Installing Neovim
Download the latest Neovim AppImage:
1curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim.appimage
Make it executable:
1chmod u+x nvim.appimage
Move it to a directory in your PATH:
1sudo mv nvim.appimage /usr/local/bin/nvim
You may also need to Install libfuse2
1sudo apt-get update 2sudo apt-get install -y libfuse2
or if you’re using Ubuntu 24.04 or a newer version
1sudo apt-get install -y libfuse2t64
Step 4: Setting Up Kickstart.nvim
Before we get started here, be sure to checkout my dotfiles in case you want to see the final result or need a reference along the way. Also feel free to fork/clone them.
Back up your existing Neovim configuration:
1mv ~/.config/nvim ~/.config/nvim.bak
Clone Kickstart.nvim:
1git clone https://github.com/nvim-lua/kickstart.nvim.git ~/.config/nvim
5. Configuring Go Development and DAP
Installing Delve Debugger (moved up):
1go install github.com/go-delve/delve/cmd/dlv@latest
Instead of creating separate files(we can do that later), we’ll modify the existing
init.lua
in thecustom/plugins
directory. Open~/.config/nvim/lua/custom/plugins/init.lua
and add the following content:
1 -- Function to find main.go file in the project incase its not in root
2local function find_main_go()
3 local root = vim.fn.getcwd()
4 local main_go = vim.fn.globpath(root, '**/main.go', 0, 1)
5 if #main_go > 0 then
6 return vim.fn.fnamemodify(main_go[1], ':h')
7 end
8 return root
9end
10
11return {
12 -- Core DAP (Debug Adapter Protocol) plugin
13 {
14 'mfussenegger/nvim-dap',
15 dependencies = {
16 -- Creates a beautiful debugger UI
17 'rcarriga/nvim-dap-ui',
18
19 -- Installs the debug adapters for you
20 'williamboman/mason.nvim',
21 'jay-babu/mason-nvim-dap.nvim',
22
23 -- Add your own debuggers here
24 'leoluz/nvim-dap-go',
25 },
26 config = function()
27 local dap = require 'dap'
28 local dapui = require 'dapui'
29
30 -- Configure Mason to automatically install DAP adapters
31 require('mason-nvim-dap').setup {
32 -- Makes a best effort to setup the various debuggers with
33 -- reasonable debug configurations
34 automatic_setup = true,
35
36 -- You can provide additional configuration to the handlers,
37 -- see mason-nvim-dap README for more information
38 handlers = {},
39
40 -- You'll need to check that you have the required things installed
41 -- online, please don't ask me how to install them :)
42 ensure_installed = {
43 -- Update this to ensure that you have the debuggers for the langs you want
44 'delve',
45 },
46 }
47
48 -- Basic debugging keymaps, feel free to change to your liking!
49 vim.keymap.set('n', '<F5>', dap.continue, { desc = 'Debug: Start/Continue' })
50 vim.keymap.set('n', '<F1>', dap.step_into, { desc = 'Debug: Step Into' })
51 vim.keymap.set('n', '<F2>', dap.step_over, { desc = 'Debug: Step Over' })
52 vim.keymap.set('n', '<F3>', dap.step_out, { desc = 'Debug: Step Out' })
53 vim.keymap.set('n', '<leader>b', dap.toggle_breakpoint, { desc = 'Debug: Toggle Breakpoint' })
54 vim.keymap.set('n', '<leader>B', function()
55 dap.set_breakpoint(vim.fn.input 'Breakpoint condition: ')
56 end, { desc = 'Debug: Set Breakpoint' })
57
58 -- Dap UI setup
59 -- For more information, see |:help nvim-dap-ui|
60 dapui.setup {
61 -- Set icons to characters that are more likely to work in every terminal.
62 -- Feel free to remove or use ones that you like more! :)
63 -- Don't feel like these are good choices.
64 icons = { expanded = '▾', collapsed = '▸', current_frame = '*' },
65 controls = {
66 icons = {
67 pause = '⏸',
68 play = '▶',
69 step_into = '⏎',
70 step_over = '⏭',
71 step_out = '⏮',
72 step_back = 'b',
73 run_last = '▶▶',
74 terminate = '⏹',
75 disconnect = '⏏',
76 },
77 },
78 }
79
80 -- Toggle to see last session result. Without this, you can't see session output in case of unhandled exception.
81 vim.keymap.set('n', '<F7>', dapui.toggle, { desc = 'Debug: See last session result' })
82
83 dap.listeners.after.event_initialized['dapui_config'] = dapui.open
84 dap.listeners.before.event_terminated['dapui_config'] = dapui.close
85 dap.listeners.before.event_exited['dapui_config'] = dapui.close
86
87 -- Install golang specific config
88 require('dap-go').setup()
89
90 -- Override dap-go's launch configuration so we can find main.go even if it isn't in the root of our project
91 dap.configurations.go = {
92 {
93 type = 'go',
94 name = 'Debug',
95 request = 'launch',
96 program = function()
97 return find_main_go()
98 end,
99 },
100 }
101 end,
102 },
103}
6. Final Configuration
- Open Neovim:
nvim
and Run:checkhealth
to ensure everything is set up correctly. - Optional: Tease apart the main
init.lua
file, ie.nvim\init.lua
.
- To do this find different sections of code you would like to pull out of the main file. For example see steps and lua snippet below of the auto format configuration that comes with kickstart.
- But before you do this be sure to uncomment
-- { import = 'custom.plugins' },
innvim\init.lua
, this will allow you to tease apart this main init.lua and place the things you tease apart innvim\custom\plugin\auto-format.lua
1-- Everything in the curly braces is alread in the nvim/init.lua, simply cut it out
2-- and place it in a file within `nvim\custom\plugin\auto-format.lua`, and be sure
3-- to include the return
4-- ie. return { paste all the code you just cut here }
5-- if you do this sytematically you can really clean up the main init.lua file at nvim root dir
6return { -- Autoformat
7 'stevearc/conform.nvim',
8 event = { 'BufWritePre' },
9 cmd = { 'ConformInfo' },
10 keys = {
11 {
12 '<leader>f',
13 function()
14 require('conform').format { async = true, lsp_format = 'fallback' }
15 end,
16 mode = '',
17 desc = '[F]ormat buffer',
18 },
19 },
20 opts = {
21 notify_on_error = false,
22 format_on_save = function(bufnr)
23 -- Disable "format_on_save lsp_fallback" for languages that don't
24 -- have a well standardized coding style. You can add additional
25 -- languages here or re-enable it for the disabled ones.
26 local disable_filetypes = { c = true, cpp = true }
27 local lsp_format_opt
28 if disable_filetypes[vim.bo[bufnr].filetype] then
29 lsp_format_opt = 'never'
30 else
31 lsp_format_opt = 'fallback'
32 end
33 return {
34 timeout_ms = 500,
35 lsp_format = lsp_format_opt,
36 }
37 end,
38 formatters_by_ft = {
39 lua = { 'stylua' },
40 -- Conform can also run multiple formatters sequentially
41 -- python = { "isort", "black" },
42 --
43 -- You can use 'stop_after_first' to run the first available formatter from the list
44 -- javascript = { "prettierd", "prettier", stop_after_first = true },
45
46 go = { 'go/fmt' }, -- NOTE: this isn't default, I added this for go formatting, the rest in this example is default
47 },
48 },
49}
- Generally this approach looks roughly as follows.
- Plugin Location:
- Plugins must be defined either in the main
nvim/init.lua
file or within thelua/custom/plugins
directory.
- Plugins must be defined either in the main
- Modular Approach:
- Gradually separating components from the main
init.lua
is an effective way to experiment with your setup. - Start by isolating small, manageable pieces of configuration.
- Gradually separating components from the main
- Hands-on Practice:
This process also provides valuable Neovim editing practice:
- Open the source file
- Use
cc
to cut the desired code - Navigate to the target location with
:Ex
- Create a new file with
%
followed by the filename (e.g.,my-new-file.lua
) - Enter insert mode with
i
, press return, thenEsc
- Paste the code with
p
- Troubleshooting:
- When encountering plugin issues, always refer to the plugin’s documentation and README.
- Check for recent GitHub issues related to the plugin for potential solutions or known problems.
- Plugin Location:
Conclusion
Now you should be fully setup to Debug your Go projects with Neovim on WSL! Obviously there still a lot that could be done to further clean up this setup/debug experience, if you looked at or used my dotfiles you might have even noticed plenty of todos as I will likely iterate on this in the future and make another post as I learn/cleanup my neovim setup.
If you have any questions or trouble, feel free to leave a comment. I really hope this was helpful to someone, and I appreciate you if you made it this far and are reading this!