Di episode ini kita akan mengubah Neovim menjadi debugger interaktif setara IDE GUI menggunakan Debug Adapter Protocol: breakpoints, step over/into/out, inspeksi variabel dan call stack, serta konfigurasi debugger untuk Go, Python, dan JavaScript/TypeScript.

Setelah di episode 21 sebelumnya kita mengintegrasikan AI coding assistant untuk menulis kode lebih cepat, pada episode kali ini kita akan membangun kemampuan yang justru membuat kode yang kalian tulis menjadi bisa dipertanggungjawabkan: debugging interaktif. Kita akan mempelajari Debug Adapter Protocol (DAP) dan mengimplementasikannya di Neovim menggunakan nvim-dap dan nvim-dap-ui, sehingga kalian bisa memasang breakpoint, menelusuri eksekusi baris demi baris, dan memeriksa variabel — persis seperti di VS Code atau IntelliJ.
Kenapa topik ini sangat penting untuk dipahami? Ada mitos yang berkembang bahwa print debugging — menambahkan fmt.Println() atau console.log() di sana-sini — sudah cukup. Memang praktis, tapi ada biaya tersembunyinya: kalian harus menebak titik mana yang perlu di-log, menunggu proses selesai, lalu membaca output yang menumpuk. Untuk bug yang kompleks — misalnya race condition, state yang berubah di banyak tempat, atau logika asinkron — print debugging akan berubah menjadi pekerjaan detektif yang lambat dan melelahkan. Debugger interaktif mengubah paradigma ini: kalian menjalankan program dengan kontrol penuh, berhenti di titik yang diinginkan, dan mengamati kondisi internalnya pada saat itu juga.
Sebagai DevOps engineer, kemampuan ini juga penting untuk memahami kode yang tidak kalian tulis sendiri — service lain, script migration, atau tooling internal. Debugger adalah cara tercepat untuk memahami perilaku nyata sebuah program. Mari mulai dari konsep protokolnya.
DAP adalah protokol komunikasi terbuka yang mendefinisikan bagaimana editor berkomunikasi dengan debugger. Arsitekturnya memisahkan dua komponen:
Ini analog dengan LSP yang kita pelajari di episode 15: seperti LSP memisahkan language intelligence dari editor, DAP memisahkan debugging intelligence dari editor. Dengan kata lain, Neovim tidak perlu tahu cara meng-debug Go atau Python secara internal — ia hanya perlu berbicara DAP, dan debug adapter spesifik bahasa yang menangani sisanya.
Neovim (DAP Client) <--JSON-RPC--> Debug Adapter (delve / debugpy / js-debug) <--> Proses aplikasiImportant
Perbedaan fundamental dengan LSP: LSP menganalisis kode (statis), sedangkan DAP menjalankan kode (dinamis). LSP menjawab pertanyaan "apa definisi fungsi ini?", DAP menjawab "mengapa fungsi ini mengembalikan nilai yang salah?" — karena DAP benar-benar menghentikan eksekusi dan memeriksa kondisi runtime. Keduanya saling melengkapi, bukan menggantikan.
nvim-dap dan nvim-dap-uireturn {
{
"mfussenegger/nvim-dap",
keys = {
{ "<F5>", function() require("dap").continue() end, desc = "DAP: Continue" },
{ "<F10>", function() require("dap").step_over() end, desc = "DAP: Step Over" },
{ "<F11>", function() require("dap").step_into() end, desc = "DAP: Step Into" },
{ "<F12>", function() require("dap").step_out() end, desc = "DAP: Step Out" },
{ "<leader>db", function() require("dap").toggle_breakpoint() end, desc = "DAP: Toggle breakpoint" },
{ "<leader>dB", function() require("dap").set_breakpoint(vim.fn.input("Breakpoint condition: ")) end,
desc = "DAP: Conditional breakpoint" },
{ "<leader>dr", function() require("dap").repl.open() end, desc = "DAP: Open REPL" },
{ "<leader>dc", function() require("dap").run_to_cursor() end, desc = "DAP: Run to cursor" },
},
},
{
"rcarriga/nvim-dap-ui",
dependencies = { "mfussenegger/nvim-dap" },
config = function()
local dapui = require("dapui")
dapui.setup()
require("dap").listeners.after.event_initialized["dapui_config"] = function()
dapui.open()
end
require("dap").listeners.after.event_terminated["dapui_config"] = function()
dapui.close()
end
require("dap").listeners.after.event_exited["dapui_config"] = function()
dapui.close()
end
end,
},
}nvim-dap adalah mesin inti (mengatur sesi debugging, komunikasi protokol), sedangkan nvim-dap-ui adalah tampilan (panel variabel, call stack, breakpoint, dan REPL). Keduanya wajib dipasang bersama.
Warning
nvim-dap-ui wajib bergantung pada nvim-dap — jangan memasang dap-ui tanpa nvim-dap. Tampilan UI hanya bisa bekerja karena ia mendengarkan event yang dipancarkan oleh nvim-dap seperti event_initialized, event_terminated, dan event_exited. Jika nvim-dap tidak ada, dap-ui akan error saat dimuat. Ini adalah dependency yang tidak bisa ditawar.
Setelah terpasang, kalian sudah bisa melakukan hal-hal berikut:
| Keymap | Aksi |
|---|---|
<leader>db | Toggle breakpoint pada baris kursor |
<leader>dB | Set conditional breakpoint (hanya berhenti jika kondisi terpenuhi) |
<F5> | Mulai/jalankan debugging (continue) |
<F10> | Step over — eksekusi baris ini lalu berhenti di baris berikutnya |
<F11> | Step into — masuk ke dalam pemanggilan fungsi pada baris ini |
<F12> | Step out — selesaikan fungsi saat ini lalu kembali ke pemanggil |
<leader>dc | Run to cursor — jalankan sampai baris kursor |
<leader>dr | Buka REPL untuk mengevaluasi ekspresi |
Tip
Perhatikan pola step debugging: Step Over (F10) melompati isi fungsi dan berhenti di baris berikutnya; Step Into (F11) masuk ke dalam fungsi yang dipanggil di baris tersebut; Step Out (F12) keluar dari fungsi saat ini. Analoginya seperti membaca buku: Step Over membaca halaman depan saja, Step Into membuka catatan kaki yang dirujuk, Step Out kembali ke alur utama.
Bagian yang paling penting (dan paling sering dilupakan) adalah mengonfigurasi debug adapter untuk bahasa yang dipakai. Tanpa konfigurasi ini, <F5> akan memberi error karena Neovim tidak tahu debugger mana yang harus dijalankan.
delve)local dap = require("dap")
dap.adapters.delve = {
type = "server",
port = "${port}",
executable = {
command = "dlv",
args = { "dap", "-l", "127.0.0.1:${port}" },
},
}
dap.configurations.go = {
{
type = "delve",
name = "Debug",
request = "launch",
program = "${fileDirname}",
},
{
type = "delve",
name = "Debug test",
request = "launch",
mode = "test",
program = "${fileDirname}",
},
}go install github.com/go-delve/delve/cmd/dlv@latestlocal dap = require("dap")
dap.adapters.python = {
type = "executable",
command = "python3",
args = { "-m", "debugpy.adapter" },
}
dap.configurations.python = {
{
type = "python",
name = "Debug file",
request = "launch",
program = "${file}",
console = "integratedTerminal",
},
{
type = "python",
name = "Debug tests",
request = "launch",
module = "pytest",
args = { "-v" },
console = "integratedTerminal",
},
}python3 -m pip install debugpyvscode-js-debug via js-debug-adapterUntuk JS/TS, pakailah adapter yang sama dengan yang dipakai VS Code. Instalasi binary-nya melalui mason.nvim (episode 15) menggunakan paket js-debug-adapter:
local dap = require("dap")
dap.adapters["pwa-node"] = {
type = "server",
host = "127.0.0.1",
port = 9229,
executable = {
command = "node",
args = {
vim.fn.stdpath("data") .. "/mason/packages/js-debug-adapter/js-debug/src/dapDebugServer.js",
},
},
}
dap.configurations.javascript = {
{
type = "pwa-node",
name = "Debug current file",
request = "launch",
program = "${file}",
cwd = vim.fn.getcwd(),
},
}
dap.configurations.typescript = dap.configurations.javascript
dap.configurations.javascriptreact = dap.configurations.javascript
dap.configurations.typescriptreact = dap.configurations.javascript:MasonInstall js-debug-adapterTip
Jika memakai mason.nvim untuk mengelola adapter DAP, pertimbangkan juga plugin pembantu mason-nvim-dap yang otomatis menghubungkan binary yang diinstall di Mason ke konfigurasi nvim-dap — kalian tidak perlu menulis dap.adapters secara manual untuk adapter yang didukungnya.
Mari kita praktikkan alur debugging lengkap pada program Go sederhana:
package main
import "fmt"
func hitungTotal(angka []int) int {
total := 0
for _, n := range angka {
total += n
}
return total
}
func main() {
data := []int{1, 2, 3, 4}
hasil := hitungTotal(data)
fmt.Println("Total:", hasil)
}Langkah-langkahnya:
main.go, letakkan kursor di baris hasil := hitungTotal(data) lalu tekan <leader>db untuk memasang breakpoint (muncul tanda lingkaran merah di gutter).<F5> untuk memulai debugging. Program berjalan hingga mencapai breakpoint, lalu berhenti.nvim-dap-ui terbuka otomatis menampilkan Variables, Call Stack, Breakpoints, dan Watches.<F10> (step over) beberapa kali untuk menelusuri eksekusi fungsi hitungTotal. Amati nilai total berubah di panel Variables.<leader>dr lalu ketik data[0] — Neovim mengevaluasinya melalui debug adapter.<F5> lagi (continue) hingga program selesai, atau tekan <leader>dc untuk run-to-cursor. Panel dap-ui akan menutup otomatis.Note
Ketika program berhenti di breakpoint, kalian tetap bisa menggerakkan kursor ke variabel dan memakai vim.api.nvim_win_get_cursor untuk membaca nilainya — tetapi cara paling nyaman adalah menggunakan hover pada nama variabel di panel Variables. Beberapa adapter juga mendukung hover to inspect ketika mouse hover di variabel.
Menjalankan debugging dengan breakpoint di setiap baris adalah cara kerja yang naif dan membuang waktu pada codebase besar. Dua fitur DAP yang harus kalian kuasai adalah conditional breakpoint dan watch expression:
<leader>dB) — breakpoint hanya memicu ketika suatu ekspresi terpenuhi. Contoh paling nyata: dalam loop 10.000 iterasi, kalian hanya ingin berhenti ketika index == 9999 atau ketika err != nil. Ini menghindari menekan <F10> berulang-ulang.len(data) dan data[0] sekaligus, sehingga saat debugging berjalan kalian langsung melihat keduanya tanpa harus mengetik ulang di REPL setiap kali program berhenti.Perhatikan bahwa conditional breakpoint dievaluasi oleh debug adapter itu sendiri, bukan oleh Neovim. Akibatnya, sintaks ekspresi mengikuti bahasa debugger — ekspresi Go ditulis dalam sintaks Go, ekspresi Python dalam sintaks Python. Error di ekspresi tersebut baru terdeteksi saat debugging dimulai, bukan saat kalian mengetikkannya.
launch vs attachDua mode utama DAP yang wajib kalian bedakan:
| Mode | Deskripsi | Kapan dipakai |
|---|---|---|
launch | Debugger menjalankan program baru, menempelkan dirinya sejak awal | Debugging harian file/test lokal |
attach | Debugger menyambung ke proses yang sudah berjalan | Bug yang hanya muncul di staging/container, atau process sudah dijalankan dengan mode debug |
Untuk attach, program target biasanya harus dijalankan dengan debugging mode terlebih dahulu — misalnya Python dengan debugpy.listen(...) di dalam kode, atau Node.js dengan --inspect. Setelah itu Neovim cukup mengarahkan DAP ke host dan port yang sudah dibuka.
| Keymap | Aksi |
|---|---|
<leader>db | Toggle breakpoint |
<leader>dB | Breakpoint kondisional |
<F5> | Continue / mulai debugging |
<F10> | Step over |
<F11> | Step into |
<F12> | Step out |
<leader>dc | Run to cursor |
<leader>dr | Buka REPL |
<leader>dn / <leader>dp | Navigasi ke breakpoint berikutnya/sebelumnya |
| Kesalahan | Gejala | Solusi |
|---|---|---|
| Adapter belum dikonfigurasi | Error no adapter found saat <F5> | Pastikan dap.adapters.<tipe> didefinisikan untuk bahasa yang dipakai |
| Debugger binary tidak terinstall | Error command not found: dlv / debugpy | Install via go install, pip, atau :MasonInstall |
| Port binding bentrok / tidak bisa bind | Adapter gagal start, error port | Gunakan type = "executable" (Neovim spawn binary) atau port yang unik |
nvim-dap-ui error "module not found" | UI tidak terbuka saat debugging | Pastikan dap-ui di-install bersama nvim-dap sebagai dependency |
| Breakpoint tidak aktif (disebut disabled) | Program tidak berhenti di titik yang diinginkan | Verifikasi bahwa adapter memuat simbol yang benar (misal build -gcflags=all=-N -l untuk Go dalam beberapa kasus) |
| Session tidak bisa restart dengan bersih | Debugging kedua error, state lama menggantung | Jalankan :DapTerminate / :DapRestart dan tutup sesi lama |
| Program tidak menemukan env var | Aplikasi berjalan tanpa konfigurasi environment | Tambahkan env = { ... } pada dap.configurations |
| dap-ui terbuka tapi panel kosong | Variables/Watches tidak menampilkan apa-apa | Pastikan debugging benar-benar berhenti di breakpoint (mode pause), bukan berjalan penuh |
Caution
Di environment perusahaan yang terisolasi (tidak ada akses internet ke registry Go/PyPI), semua binary debugger harus di-install terlebih dahulu saat membangun image/container development. Jangan pernah menunda instalasi dlv, debugpy, atau js-debug-adapter sampai sesi debugging — dependency yang tidak tersedia saat runtime akan menghancurkan alur kerja kalian tepat di saat genting.
Tip
Untuk debugging aplikasi yang berjalan di remote container atau Kubernetes, pertimbangkan pola request = "attach" daripada launch. Dengan attach, debugger menyambung ke proses yang sudah berjalan — sangat berguna untuk memeriksa bug yang hanya muncul di environment staging. Pastikan port debugger di-expose dan firewall mengizinkan koneksi.
Pada episode 22 ini kita telah mengubah Neovim menjadi debugger interaktif setara IDE GUI: memahami Debug Adapter Protocol dan arsitektur client-adapter yang memisahkan debugging intelligence dari editor, memasang nvim-dap sebagai mesin inti dan nvim-dap-ui sebagai tampilan panel variabel/call stack/breakpoint, mengonfigurasi debug adapter untuk Go (Delve), Python (debugpy), dan JavaScript/TypeScript (vscode-js-debug) , serta berlatih walkthrough debugging lengkap dengan breakpoint, step over/into/out, dan REPL.
Dengan kemampuan ini, kalian tidak perlu lagi berpindah ke IDE lain hanya untuk debugging. Neovim sekarang adalah production-grade development environment yang utuh: editing cepat, terminal terintegrasi, AI assistance, dan debugging interaktif — semuanya di satu tempat.
Kita telah menyelesaikan Fase 5 dari seri Belajar Neovim. Di episode 23 selanjutnya, kita akan masuk ke Fase 6 dan membahas Profile & Performance Optimization — mengukur startup time, menemukan plugin yang lambat, dan memastikan Neovim tetap menyala dalam 50ms meskipun berisi puluhan plugin. Pastikan tetap semangat!