Neovim LSP and DAP using Lua
LSP and DAP for Python, Golang, Rust, and Typescript/Javascript.

Overview
In my previous article I walked through with you on how to convert existing .vimrc
to a Lua based configuration and set up LSP for Lua.
In this article let’s add LSP and DAP support. Particularly I will focus on Python, Typescript, Golang and Rust.
Installation of Language Servers
Let’s get started by installing the language server.
If you are using coc.nvim, normally the language server is installed automatically for you. For Neovim LSP, there are available plugins to automate this process. Here I will go through the manual installation steps.
Python
For Python, let’s install pyright.
$ npm install -g pyright
Golang
For Golang, let’s install gopls.
$ GO111MODULE=on go get golang.org/x/tools/gopls@latest
Rust
For Rust, let’s use rust_analyzer. The easiest way to install is to download the binary, copy to a folder, and add the folder to environment PATH.
Typescript
For Typescript, let’s install typescript-language-server.
$ npm install -g typescript-language-server
Configure LSP
Plugins
Under plugins.lua,
install nvim-lspconfig and completion-nvim.
-- LSP and completion
use { 'neovim/nvim-lspconfig' }
use { 'nvim-lua/completion-nvim' }
Run :luafile %
and then :PackerInstall
.
Configuration
The current configuration already supports Lua LSP. I am going to leverage existing configuration.
- Under
lua
folder, create a new folder calledlang
. - Create a new file called
init.lua
underlang
folder.
In the main init.lua,
I import the new lang
module.
-- LSP
require('lang')-- DAP
require('dbg')
Now Neovim should have the correct language server configured for Lua, Python, Golang, Rust, and Typescript.
Note: In case language server is not started when you are editing a file, type :LspInfo
for troubleshooting.




Configure snippets
For a Lua based snippets plugin, you can use snippets.nvim. However, there will not be any ready snippets for use, and you have to manually create the snippets that you need.
I am going to use UltiSnips.
-- Snippets
use { 'honza/vim-snippets' }
use { 'SirVer/ultisnips' }
Run :luafile %
and then :PackerInstall
.
In completion.lua
, add the following line.
local utils = require('utils')utils.opt('o', 'completeopt', 'menuone,noinsert,noselect')
vim.cmd [[set shortmess+=c]]
vim.g.completion_confirm_key = ""
vim.g.completion_matching_strategy_list = {'exact', 'substring', 'fuzzy'}
vim.g.diagnostic_enable_virtual_text = 1
vim.g.completion_enable_snippet = 'UltiSnips'
vim.g.completion_enable_auto_popup = 1
And snippets should work now.

Configure DAP
Plugins
Under plugins.lua,
install nvim-dap, telescope-dap.nvim and nvim-dap-python.
-- DAP
use { 'mfussenegger/nvim-dap' }
use { 'nvim-telescope/telescope-dap.nvim' }
use { 'mfussenegger/nvim-dap-python' } -- Python
Run :luafile %
and then :PackerInstall
.
Key Mappings
Let’s define the key mappings for debugging. You can change them depending on your preferences.
Note I also define the key mappings for telescope-dap.

DAP for Python
For Python debugging, I use nvim-dap-python which requires nvim-treesitter and debugpy. You can refer to my previous article on how to install them.
Create a file called python.lua
under dbg
folder with the following content. Change the Python path accordingly.
require('dap-python').setup('~/miniconda3/bin/python')
Import the module in dbg/init.lua
require('telescope').load_extension('dap')
require('dbg.python').....
Restart Neovim and DAP should work now for Python.

DAP for Rust
For Rust debugging, make sure you have LLVM installed and have lldb-vscode available.
E.g. For MacOS, I can install llvm
using brew
.
$ brew install llvm
And lldb-vscode
should be available under /usr/local/Cellar/llvm

Make sure you add lldb-vscode
to your environment path!
Create a file called rust.lua
under dbg
folder with the following content. Change the program path accordingly.
local dap = require "dap"
dap.adapters.rust = {
type = 'executable',
attach = {
pidProperty = "pid",
pidSelect = "ask"
},
command = 'lldb-vscode', -- my binary was called 'lldb-vscode-11'
env = {
LLDB_LAUNCH_FLAG_LAUNCH_IN_TTY = "YES"
},
name = "lldb"
}dap.configurations.rust = {
{
type = "rust",
name = "Debug",
request = "launch",
program= "/Users/mengwangk/workspace/development/rust-app/target/debug/myapp1"
}
}
Import the module in dbg/init.lua
require('telescope').load_extension('dap')
require('dbg.rust').....
Restart Neovim and DAP should work now for Rust.

DAP for Golang
For Golang debugging, make sure you have delve installed and available as part of your environment path.
$ go install github.com/go-delve/delve/cmd/dlv@latest
Create a file called go.lua
under dbg
folder with the following content.
local dap = require "dap"dap.adapters.go = function(callback, config)
local handle
local pid_or_err
local port = 38697
handle, pid_or_err =
vim.loop.spawn(
"dlv",
{
args = {"dap", "-l", "127.0.0.1:" .. port},
detached = true
},
function(code)
handle:close()
print("Delve exited with exit code: " .. code)
end
)
----should we wait for delve to start???
--vim.defer_fn(
--function()
--dap.repl.open()
--callback({type = "server", host = "127.0.0.1", port = port})
--end,
--100)dap.repl.open()
callback({type = "server", host = "127.0.0.1", port = port})
end
-- https://github.com/go-delve/delve/blob/master/Documentation/usage/dlv_dap.md
dap.configurations.go = {
{
type = "go",
name = "Debug",
request = "launch",
program = "${file}"
}
}
Import the module in dbg/init.lua
require('telescope').load_extension('dap')
require('dbg.go').....
Restart Neovim and DAP should work now for Golang.
In case you hit errors, e.g. “connection refused”, try to start delve
in DAP mode manually from you project folder by running the following command before start debugging. Do comment out vim.loop.spawn
.
$ dlv dap -l 127.0.0.1:38697 --log --log-output="dap"


Note: Debugging Go application using delve directly is still experimental and is not very stable. You can also check out the option to use vscode-go which I think is more stable.
DAP for Javascript/Node
For Javascript/Node debugging, make sure you have node-debug2 installed as described in the documentation.
Create a file called typescript.lua
under dbg
folder with the following content. Change the path to node-debug2
accordingly.
local dap = require "dap"dap.adapters.node2 = {
type = 'executable',
command = 'node',
args = {os.getenv('HOME') .. '/.xdg_home/nvim/extensions/vscode-node-debug2/out/src/nodeDebug.js'},
}dap.configurations.javascript = {
{
type = 'node2',
request = 'launch',
-- program = { os.getenv('HOME') .. '/workspace/development/alpha2phi/python-apps/debugging/hello.js'},
program = '${workspaceFolder}/${file}',
cwd = vim.fn.getcwd(),
sourceMaps = true,
protocol = 'inspector',
console = 'integratedTerminal',
},
}
Import the module in dbg/init.lua
require('telescope').load_extension('dap')
require('dbg.typescript').....
Restart Neovim and DAP should work now for Javascript/Node.

DAP configuration for other languages can be found at the documentation. Depending on the language you use, you may need to trials and errors to see if it works for you. You can also check out my repository here.
Another option for debugging is vimspector. You can refer to the following articles on how I set it up for Python, Golang, Rust and Javascript/Typescript debugging.
Startup Screen
This is not related to LSP or DAP but let’s also create a startup screen for the development environment.
A popular option is vim-startify, but let’s try dashboard-nvim here.
Add the following line in plugins.lua
and install it.
-- Dashboard
use { 'glepnir/dashboard-nvim' }
Create a file called dashboard.vim
under the plugin
folder.
let g:dashboard_default_executive ='telescope'
let g:dashboard_custom_footer = ['']
let g:dashboard_custom_header = [
\ '',
\' /\ /\ /\',
\' /\//\\/\ /\//\\/\ /\//\\/\',
\' /\//\\\///\\/\//\\\///\\/\//\\\///\\/\',
\'//\\\//\/\\///\\\//\/\\///\\\//\/\\///\\',
\'\\//\/ \/\\//',
\' \/ \/',
\' /\ /\',
\'//\\ Neovim IDE with LSP and DAP //\\',
\'\\// \\//',
\' \/ \/',
\' /\ /\',
\'//\\/\ /\//\\',
\'\\///\\/\//\\\///\\/\//\\\///\\/\//\\\//',
\' \/\\///\\\//\/\\///\\\//\/\\///\\\//\/',
\' \/\\//\/ \/\\//\/ \/\\//\/',
\' \/ \/ \/',
\'',
\ '',
\ ]
In telescope.vim
under plugin folder, let’s remap the shortcuts and keys.
" Find files using Telescope command-line sugar.
nnoremap <silent> <leader>ff :DashboardFindFile<CR>
nnoremap <silent> <leader>fg :Telescope live_grep<cr>
nnoremap <silent> <leader>fb :Telescope buffers<cr>
nnoremap <silent> <leader>ft :Telescope help_tags<cr>
nnoremap <silent> <leader>fh :DashboardFindHistory<CR>
nnoremap <silent> <leader>fc :DashboardChangeColorscheme<CR>
nnoremap <silent> <leader>fw :DashboardFindWord<CR>
nnoremap <silent> <leader>fm :DashboardJumpMark<CR>
nnoremap <silent> <leader>fn :DashboardNewFile<CR>nmap <leader>fss :<C-u>SessionSave<CR>
nmap <leader>fsl :<C-u>SessionLoad<CR>let g:dashboard_custom_shortcut={
\ 'last_session' : '<Leader> fs l',
\ 'find_history' : '<Leader> f h',
\ 'find_file' : '<Leader> f f',
\ 'new_file' : '<Leader> f n',
\ 'change_colorscheme' : '<Leader> f c',
\ 'find_word' : '<Leader> f w',
\ 'book_marks' : '<Leader> f m',
\ }
Restart Neovim and the dashboard should come up now.

Summary
Configuring LSP for Neovim is relatively easy. However configuring DAP is still quite tedious and challenging. Currently 2 popular options are available — nvim-dap
and vimspector
and you may need to experiment with both to see which one fits your development workflow.
The dotfiles I used can be found at this repository.