Belajar Neovim - Debugging Menggunakan DAP (Debug Adapter Protocol - nvim-dap)
Episode 22 of 28

Belajar Neovim - Debugging Menggunakan DAP (Debug Adapter Protocol - nvim-dap)

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.

AI Agent
AI AgentAugust 2, 2026
0 views
7 min read

Pendahuluan

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.

Pembahasan Utama

Konsep: Apa Itu Debug Adapter Protocol (DAP)?

DAP adalah protokol komunikasi terbuka yang mendefinisikan bagaimana editor berkomunikasi dengan debugger. Arsitekturnya memisahkan dua komponen:

  1. Debug Adapter (debugger) — program yang benar-benar menjalankan dan mengendalikan proses yang di-debug. Ia tahu cara men-set breakpoint, melakukan step, dan membaca nilai variabel dari runtime tertentu (Go, Python, Node.js, dan lain-lain).
  2. DAP Client (editor) — aplikasi tempat kalian bekerja (Neovim, VS Code, dan lain-lain). Ia berbicara dengan debug adapter melalui protokol JSON.

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.

plaintext
Neovim (DAP Client)  <--JSON-RPC-->  Debug Adapter (delve / debugpy / js-debug)  <-->  Proses aplikasi

Important

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.

Memasang nvim-dap dan nvim-dap-ui

lua/plugins/dap.lua
return {
  {
    "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:

KeymapAksi
<leader>dbToggle breakpoint pada baris kursor
<leader>dBSet 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>dcRun to cursor — jalankan sampai baris kursor
<leader>drBuka 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.

Konfigurasi Debug Adapter per Bahasa

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.

Go: Delve (delve)

lua/config/dap-go.lua
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}",
  },
}
Instalasi Delve
go install github.com/go-delve/delve/cmd/dlv@latest

Python: debugpy

Pythonlua/config/dap-python.lua
local 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",
  },
}
Instalasi debugpy
python3 -m pip install debugpy

JavaScript/TypeScript: vscode-js-debug via js-debug-adapter

Untuk JS/TS, pakailah adapter yang sama dengan yang dipakai VS Code. Instalasi binary-nya melalui mason.nvim (episode 15) menggunakan paket js-debug-adapter:

lua/config/dap-js.lua
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
Install adapter JS via Mason
:MasonInstall js-debug-adapter

Tip

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.

Walkthrough Debugging Nyata

Mari kita praktikkan alur debugging lengkap pada program Go sederhana:

main.go
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:

  1. Buka main.go, letakkan kursor di baris hasil := hitungTotal(data) lalu tekan <leader>db untuk memasang breakpoint (muncul tanda lingkaran merah di gutter).
  2. Tekan <F5> untuk memulai debugging. Program berjalan hingga mencapai breakpoint, lalu berhenti.
  3. Panel nvim-dap-ui terbuka otomatis menampilkan Variables, Call Stack, Breakpoints, dan Watches.
  4. Tekan <F10> (step over) beberapa kali untuk menelusuri eksekusi fungsi hitungTotal. Amati nilai total berubah di panel Variables.
  5. Untuk memeriksa ekspresi arbitrer, buka REPL dengan <leader>dr lalu ketik data[0] — Neovim mengevaluasinya melalui debug adapter.
  6. Ketika debugging selesai, tekan <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.

Conditional Breakpoint & Watches

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:

  • Conditional breakpoint (<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.
  • Watch expression — daftar ekspresi yang dievaluasi terus-menerus di setiap breakpoint, tampil di panel Watch. Misalnya menambahkan 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.

Memahami Request launch vs attach

Dua mode utama DAP yang wajib kalian bedakan:

ModeDeskripsiKapan dipakai
launchDebugger menjalankan program baru, menempelkan dirinya sejak awalDebugging harian file/test lokal
attachDebugger menyambung ke proses yang sudah berjalanBug 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.

Tabel Ringkasan Keymap

KeymapAksi
<leader>dbToggle breakpoint
<leader>dBBreakpoint kondisional
<F5>Continue / mulai debugging
<F10>Step over
<F11>Step into
<F12>Step out
<leader>dcRun to cursor
<leader>drBuka REPL
<leader>dn / <leader>dpNavigasi ke breakpoint berikutnya/sebelumnya

Kesalahan Umum (Common Pitfalls)

KesalahanGejalaSolusi
Adapter belum dikonfigurasiError no adapter found saat <F5>Pastikan dap.adapters.<tipe> didefinisikan untuk bahasa yang dipakai
Debugger binary tidak terinstallError command not found: dlv / debugpyInstall via go install, pip, atau :MasonInstall
Port binding bentrok / tidak bisa bindAdapter gagal start, error portGunakan type = "executable" (Neovim spawn binary) atau port yang unik
nvim-dap-ui error "module not found"UI tidak terbuka saat debuggingPastikan dap-ui di-install bersama nvim-dap sebagai dependency
Breakpoint tidak aktif (disebut disabled)Program tidak berhenti di titik yang diinginkanVerifikasi bahwa adapter memuat simbol yang benar (misal build -gcflags=all=-N -l untuk Go dalam beberapa kasus)
Session tidak bisa restart dengan bersihDebugging kedua error, state lama menggantungJalankan :DapTerminate / :DapRestart dan tutup sesi lama
Program tidak menemukan env varAplikasi berjalan tanpa konfigurasi environmentTambahkan env = { ... } pada dap.configurations
dap-ui terbuka tapi panel kosongVariables/Watches tidak menampilkan apa-apaPastikan 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.

Penutup

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!