CB

Setting up, Running, and Debugging Go Applications with Neovim on WSL

· Cole Boren


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

  1. Open PowerShell as Administrator and run:

    1wsl --install
  2. Restart your computer when prompted.

  3. After restart, open Ubuntu from the Start menu to complete the setup.

Step 2: Installing Go

  1. Update your package list:

    1sudo apt update
  2. Remove any existing Go installation:

    1sudo rm -rf /usr/local/go
  3. 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
  4. Extract Go to /usr/local:

    1sudo tar -C /usr/local -xzf go1.22.7.linux-amd64.tar.gz
  5. Set up your Go environment:

    1echo 'export GOROOT=/usr/local/go' >> ~/.bashrc
    2echo 'export PATH=$GOROOT/bin:$PATH' >> ~/.bashrc
    3source ~/.bashrc
  6. Verify the installation:

    1go version

Step 3: Installing Neovim

  1. Download the latest Neovim AppImage:

    1curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim.appimage
  2. Make it executable:

    1chmod u+x nvim.appimage
  3. Move it to a directory in your PATH:

    1sudo mv nvim.appimage /usr/local/bin/nvim
  4. 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

  1. 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.

  2. Back up your existing Neovim configuration:

    1mv ~/.config/nvim ~/.config/nvim.bak
  3. Clone Kickstart.nvim:

    1git clone https://github.com/nvim-lua/kickstart.nvim.git ~/.config/nvim

5. Configuring Go Development and DAP

  1. Installing Delve Debugger (moved up):

    1go install github.com/go-delve/delve/cmd/dlv@latest
  2. Instead of creating separate files(we can do that later), we’ll modify the existing init.lua in the custom/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

  1. Open Neovim: nvim and Run :checkhealth to ensure everything is set up correctly.
  2. Optional: Tease apart the main init.lua file, ie. nvim\init.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}

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!

#neovim #configuration #go #wsl #debugging

Reply to this post by email ↪