Di episode ini kita akan menghubungkan Neovim ke Language Server Protocol (LSP) untuk mendapatkan fitur IDE seperti go-to-definition, rename symbol, dan code action, plus mengelola language server secara visual dengan mason.nvim.

Setelah di episode 14 sebelumnya kita membangun pemahaman kode berbasis grammar dengan nvim-treesitter — syntax highlighting yang akurat, code folding, dan textobjects — pada episode kali ini kita melompat ke level yang lebih tinggi: kemampuan editor untuk benar-benar memahami semantik kode kalian. Kita akan menghubungkan Neovim ke Language Server Protocol (LSP) dan mengelola semua language server dengan mason.nvim.
Kenapa ini penting di dunia nyata? Bayangkan kalian sedang melakukan refactor di tim backend: mengganti nama method getUserById menjadi findUser. Tanpa LSP, kalian harus mencari semua pemanggilan secara manual — berisiko kelewat satu file, atau — lebih parah — kelewat satu pemanggilan yang menimbulkan bug runtime yang baru ketahuan di production. Dengan LSP, satu perintah rename yang sadar-semantik menggantikan semua referensi di seluruh project, termasuk file yang tidak sedang terbuka. Inilah pembeda utama antara "text editor" dan "IDE", dan kini kalian bisa mendapatkannya tanpa meninggalkan Neovim.
LSP memecahkan masalah besar yang mengganggu dunia editor selama bertahun-tahun: setiap bahasa punya tooling sendiri (compiler, linter, type checker), dan setiap editor harus berintegrasi dengan masing-masing tooling secara terpisah. Hasilnya pekerjaan ganda yang masif.
LSP menstandarkan semua itu dengan arsitektur client-server:
┌─────────────────┐ ┌─────────────────┐
│ Neovim Client │ JSON-RPC │ Language Server │
│ (nvim-lspconfig) ◄────────► (tsserver, gopls, │
│ ─────────────── │ over │ pyright, ...) │
│ • gd, K, gr │ stdio │ • parse project │
│ • diagnostics │ WebSocket│ • type check │
│ • completion │ │ • indexing │
└─────────────────┘ └─────────────────┘Penjelasan setiap komponen:
gopls untuk Go, tsserver untuk TypeScript, pyright untuk Python.vim.lsp + plugin nvim-lspconfig yang mengurus wiring.textDocument/definition (client bertanya "di mana definisi simbol ini?") dan textDocument/publishDiagnostics (server melaporkan "ada error di baris 12").Konsep kuncinya: server tidak peduli editor apa yang dipakai. Server yang sama melayani VS Code, Neovim, Emacs, dan lain-lain. Sekali sebuah language server ditulis, semua editor bisa memanfaatkannya. Inilah kenapa Microsoft — pencipta LSP pada 2016 — menyebutnya "the protocol of the future": kalian tidak lagi bergantung pada tim tertentu untuk mendukung editor kalian.
Note
Satu mental model penting: language server berjalan sebagai proses terpisah yang selalu hidup selama Neovim aktif. Dia menyimpan index project di memory-nya sendiri. Karena itu respon gd terasa instan bahkan di project besar — pencarian tidak dilakukan per-file, melainkan lewat basis data yang sudah dibangun server. Trade-off-nya, server mengonsumsi RAM sendiri; wajar jika Neovim dengan 3-4 server aktif memakai beberapa ratus MB RAM ekstra.
Integrasi LSP di Neovim modern dibangun dari tiga lapisan plugin yang bekerja berurutan:
| Plugin | Peran | Analogi |
|---|---|---|
mason.nvim | Install & manage server binary | App Store (Play Store / npm install) |
mason-lspconfig.nvim | Jembatan antara Mason & LSP config | Adapter / installer yang menghubungkan app ke sistem |
nvim-lspconfig | Konfigurasi tiap server + default settings | Pengemudi yang tahu cara "mengemudikan" masing-masing server |
Mason bukanlah bagian dari LSP itu sendiri — ia adalah package manager untuk editor tools: language server, linter, dan formatter. Kita akan memanfaatkannya lagi di episode 17 untuk linter & formatter.
return {
{
"williamboman/mason.nvim",
cmd = { "Mason", "MasonInstall", "MasonUninstall", "MasonUpdate" },
config = function()
require("mason").setup({
ui = {
border = "rounded",
icons = {
package_installed = "✓",
package_pending = "➜",
package_uninstalled = "✗",
},
},
})
end,
},
}Mengapa cmd = { "Mason", ... }? Ini adalah lazy-loading berbasis command yang dibahas di episode 9 — plugin Mason hanya dimuat saat kalian benar-benar membuka UI-nya (:Mason) atau menjalankan perintah install-nya. Karena Mason jarang dipakai setiap menit, membebani startup time hanya untuk itu tidak masuk akal.
Sekarang jalankan :Mason di dalam Neovim. Kalian akan melihat UI interaktif yang menampilkan ratusan package. Gunakan / untuk mencari, lalu tekan i untuk install package yang disorot, u untuk uninstall, dan X untuk update. Anggap ini seperti marketplace: semua language server, linter, dan formatter ada di satu tempat, ter-install ke direktori ~/.local/share/nvim/mason secara otomatis tanpa harus repot mengatur PATH.
Setelah Mason mengelola binary, kita perlu memberi tahu nvim-lspconfig server mana yang harus dijalankan dan dengan konfigurasi apa. Berikut konfigurasi lengkapnya:
return {
{
"williamboman/mason-lspconfig.nvim",
dependencies = { "williamboman/mason.nvim" },
opts = {
ensure_installed = {
"lua_ls", -- Lua
"tsserver", -- TypeScript / JavaScript
"pyright", -- Python
"gopls", -- Go
"bashls", -- Bash / Shell
"yamlls", -- YAML
"jsonls", -- JSON
"dockerls", -- Dockerfile
},
automatic_enable = true,
},
},
{
"neovim/nvim-lspconfig",
dependencies = {
"williamboman/mason.nvim",
"williamboman/mason-lspconfig.nvim",
},
config = function()
local capabilities = vim.lsp.protocol.make_client_capabilities()
-- Foundation untuk nvim-cmp di episode 16
capabilities.textDocument.completion.completionItem.snippetSupport = true
local lspconfig = require("lspconfig")
local on_attach = function(_, bufnr)
local opts = { buffer = bufnr, silent = true }
vim.keymap.set("n", "gd", vim.lsp.buf.definition, opts)
vim.keymap.set("n", "gr", vim.lsp.buf.references, opts)
vim.keymap.set("n", "K", vim.lsp.buf.hover, opts)
vim.keymap.set("n", "<leader>rn", vim.lsp.buf.rename, opts)
vim.keymap.set("n", "<leader>ca", vim.lsp.buf.code_action, opts)
vim.keymap.set("n", "<leader>e", vim.diagnostic.open_float, opts)
vim.keymap.set("n", "[d", vim.diagnostic.goto_prev, opts)
vim.keymap.set("n", "]d", vim.diagnostic.goto_next, opts)
end
require("mason-lspconfig").setup({
handlers = {
function(server)
lspconfig[server].setup({
capabilities = capabilities,
on_attach = on_attach,
})
end,
},
})
end,
},
}Mari kita bedah bagian pentingnya:
mason-lspconfig.opts.ensure_installed — daftar server yang otomatis di-install Mason saat config pertama kali dimuat. Mirip ensure_installed pada treesitter.automatic_enable = true — server yang terinstall otomatis diaktifkan untuk filetype yang sesuai.handlers — fungsi yang dipanggil untuk setiap server yang terdeteksi. Ini pola DRY: satu setup() dengan capabilities dan on_attach yang sama berlaku untuk semua server, tanpa menulis blok per-server. Jika sebuah server butuh konfigurasi khusus (misal lua_ls dengan settings), kalian bisa override dengan handler bernama spesifik.on_attach — fungsi yang dipanggil setiap kali sebuah server ter-attach ke buffer. Di sinilah semua keymap LSP didefinisikan, dengan buffer = bufnr agar hanya berlaku untuk buffer yang sedang aktif.Tip
Trik debugging yang wajib dihapal: :LspInfo untuk melihat server apa yang aktif di buffer saat ini beserta statusnya, dan :LspLog untuk membuka file log yang bisa menunjukkan kenapa sebuah server gagal attach. Kombinasi keduanya menyelesaikan 90% masalah LSP.
Sekarang kita definisikan keymap LSP yang menjadi standar di hampir semua distribusi Neovim modern (LazyVim, NvChad, dan lain-lain menggunakan pola yang sama):
| Shortcut | Aksi | Command Neovim | Fungsi |
|---|---|---|---|
gd | Go to definition | vim.lsp.buf.definition | Lompat ke deklarasi simbol di bawah kursor |
gr | References | vim.lsp.buf.references | Tampilkan semua referensi simbol di project |
K | Hover | vim.lsp.buf.hover | Tampilkan dokumentasi + type signature |
<leader>rn | Rename | vim.lsp.buf.rename | Rename simbol di seluruh project sekaligus |
<leader>ca | Code action | vim.lsp.buf.code_action | Tampilkan aksi kontekstual (quick fix, refactor, import) |
<leader>e | Diagnostics popup | vim.diagnostic.open_float | Tampilkan detail error/warning di baris kursor |
[d / ]d | Prev/Next diagnostic | vim.diagnostic.goto_prev | Lompat antar error di buffer |
Cara penggunaan singkat di kehidupan nyata:
gd — kalian langsung melompat ke definisinya. Tekan Ctrl+o untuk kembali ke posisi semula (jump list, episode 3).K pada sebuah fungsi — muncul jendela dokumentasi berisi signature dan docstring, tanpa membuka browser.<leader>rn, ketik nama baru, tekan Enter — semua referensi di seluruh project berubah, bahkan di file yang tidak terbuka. Ini yang membuat refactor di Neovim senyaman di IDE GUI.<leader>ca saat ada garis merah — muncul daftar quick fix seperti "import symbol ini" atau "auto-fix typo".Sekarang mari kita rangkai semua keymap LSP menjadi satu alur kerja yang realistis — menulis fungsi baru di project TypeScript:
1. Ketik nama fungsi baru → muncul warning "x is declared but never used" (diagnostic).
2. Tekan K pada sebuah tipe → dokumentasi & type signature muncul, tanpa buka browser.
3. Saat memanggil fungsi dari library → gd untuk lompat ke definisinya, Ctrl+o kembali.
4. Error muncul di baris 12 → ]d lompat ke sana, <leader>e lihat detail lengkap.
5. Ingin ubah nama variabel di seluruh project → <leader>rn, ketik baru, Enter.
6. Mau auto-import symbol → letakkan kursor pada nama, <leader>ca, pilih "Add import".Perhatikan alurnya: penulisan, verifikasi, refactor, dan perbaikan semua terjadi dalam satu editor, tanpa meninggalkan buffer dan tanpa berpindah konteks. Inilah yang dimaksud editor memahami kode — bukan sekadar mewarnai teks.
Tip
Ada satu keymap bonus yang sering dilupakan: signature help. Dengan default Neovim, tekan <C-x><C-o> dalam insert mode untuk memicu completion LSP secara manual, dan tambahkan vim.keymap.set("n", "K", vim.lsp.buf.hover, ...) — sudah kita pasang. Untuk signature function saat mengetik argumen, banyak developer menambah keymap <C-k> untuk vim.lsp.buf.signature_help. Tapi ingat: saat nvim-cmp sudah aktif di episode 16, signature help dan completion LSP akan menyatu dalam satu popup.
Tiga server berikut adalah yang paling sering dipakai tim production. Perhatikan bagaimana masing-masing punya kebutuhan spesifik:
require("lspconfig").tsserver.setup({
capabilities = capabilities,
on_attach = on_attach,
root_dir = require("lspconfig").util.root_pattern("package.json", "tsconfig.json", ".git"),
single_file_support = false,
})Poin penting per bahasa:
root_dir berbasis package.json/tsconfig.json — server hanya hidup jika project punya salah satunya. single_file_support = false membuat file .ts yang berdiri sendiri (tanpa project) tidak memicu server, karena tanpa tsconfig type-checking akan setengah hati.settings.python.analysis untuk mengontrol seberapa agresif type checking-nya. basic adalah titik tengah yang baik antara akurasi dan noise.staticcheck — static analysis engine yang menangkap bug yang tidak dideteksi compiler. Aktifkan selalu untuk codebase Go.| Kesalahan | Gejala | Solusi |
|---|---|---|
| Server tidak terinstall | Error client X not available atau spawn ... failed | Install via :MasonInstall <server>; pastikan nama server di ensure_installed benar |
| Server attach tapi fitur mati | gd/gr tidak melakukan apa-apa | Cek :LspInfo; pastikan capabilities & on_attach dikirim lewat handler |
| Completion tidak muncul | Popup autocomplete LSP kosong | Fitur ini butuh nvim-cmp (episode 16); di episode ini capabilities sudah disiapkan |
| Diagnostics hanya muncul setelah disimpan | Error tidak muncul real-time saat mengetik | Normal — banyak server publikasikan diagnosa saat file berubah; pastikan tidak ada yang meng-override |
pyright vs basedpyright kebingungan | Package tidak ditemukan di Mason | Di Mason nama packagenya basedpyright untuk versi fork; nama pyright adalah yang resmi |
Lupa snippetSupport | Snippet dari LSP tidak pernah muncul | Pastikan capabilities.textDocument.completion.completionItem.snippetSupport = true (wajib sebelum nvim-cmp) |
lua_ls tidak mengenal config | vim.opt dan vim.api muncul sebagai undefined | lua_ls butuh setting Lua.workspace.library atau diagnostics.globals untuk mengenal API Neovim |
Warning
Kesalahan paling membingungkan bagi pemula: menekan gd dan tidak terjadi apa-apa padahal server sudah aktif. Penyebab paling umum kedua setelah server belum install adalah keymap LSP yang didefinisikan di luar on_attach — keymap global tanpa buffer = bufnr akan ditimpa oleh keymap per-buffer yang aktif. Selalu definisikan keymap LSP di dalam on_attach, atau pastikan menggunakan buffer di keymap global.
Pada episode 15 ini kita telah memahami arsitektur client-server LSP dan mengapa ia menjadi standar universal yang memecahkan fragmentasi tooling editor. Kita telah membangun pipeline lengkap: mason.nvim sebagai package manager server, mason-lspconfig.nvim sebagai jembatan, dan nvim-lspconfig sebagai konfigurator — plus keymap LSP standar (gd, gr, K, <leader>rn, <leader>ca, <leader>e) dan konfigurasi spesifik untuk TypeScript, Python, dan Go.
Kalian kini memiliki Neovim yang memahami kode secara semantik, setara IDE komersial, namun berjalan di terminal. Namun ada satu kekosongan yang masih terasa: ketika mengetik, tidak ada popup autocompletion yang menampilkan saran dari language server ini.
Di episode 16 selanjutnya, kita akan mengisi kekosongan tersebut dengan Autocompletion Engine menggunakan nvim-cmp & Snippets — popup completion yang memadukan saran LSP, buffer, path, dan snippet dalam satu antarmuka yang gesit. Pastikan tetap semangat!