Belajar Neovim - Built-in LSP (Language Server Protocol) & mason.nvim
Episode 15 of 28

Belajar Neovim - Built-in LSP (Language Server Protocol) & mason.nvim

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.

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

Pendahuluan

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.

Pembahasan Utama

Memahami Arsitektur LSP: Client-Server

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:

Arsitektur LSP
┌─────────────────┐         ┌─────────────────┐
│  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:

  • Language Server — proses terpisah (bisa berjalan di mesin berbeda!) yang memahami satu bahasa secara mendalam. Ia mem-parse project kalian, melakukan type checking, dan membangun basis data simbol global. Contoh: gopls untuk Go, tsserver untuk TypeScript, pyright untuk Python.
  • Client (Neovim) — program yang menampilkan hasil ke kalian dan mengirim permintaan. Di Neovim, ini adalah API bawaan vim.lsp + plugin nvim-lspconfig yang mengurus wiring.
  • Protocol (JSON-RPC) — bahasa komunikasi. Kedua sisi bertukar pesan terstruktur seperti 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.

Peran Tiga Plugin dalam Satu Pipeline

Integrasi LSP di Neovim modern dibangun dari tiga lapisan plugin yang bekerja berurutan:

PluginPeranAnalogi
mason.nvimInstall & manage server binaryApp Store (Play Store / npm install)
mason-lspconfig.nvimJembatan antara Mason & LSP configAdapter / installer yang menghubungkan app ke sistem
nvim-lspconfigKonfigurasi tiap server + default settingsPengemudi 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.

Konfigurasi mason.nvim dengan lazy.nvim

lua/plugins/mason.lua
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.

Integrasi mason-lspconfig & nvim-lspconfig

Setelah Mason mengelola binary, kita perlu memberi tahu nvim-lspconfig server mana yang harus dijalankan dan dengan konfigurasi apa. Berikut konfigurasi lengkapnya:

lua/plugins/lsp.lua
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.

Keymaps LSP: Pintu Gerbang Fitur IDE

Sekarang kita definisikan keymap LSP yang menjadi standar di hampir semua distribusi Neovim modern (LazyVim, NvChad, dan lain-lain menggunakan pola yang sama):

ShortcutAksiCommand NeovimFungsi
gdGo to definitionvim.lsp.buf.definitionLompat ke deklarasi simbol di bawah kursor
grReferencesvim.lsp.buf.referencesTampilkan semua referensi simbol di project
KHovervim.lsp.buf.hoverTampilkan dokumentasi + type signature
<leader>rnRenamevim.lsp.buf.renameRename simbol di seluruh project sekaligus
<leader>caCode actionvim.lsp.buf.code_actionTampilkan aksi kontekstual (quick fix, refactor, import)
<leader>eDiagnostics popupvim.diagnostic.open_floatTampilkan detail error/warning di baris kursor
[d / ]dPrev/Next diagnosticvim.diagnostic.goto_prevLompat antar error di buffer

Cara penggunaan singkat di kehidupan nyata:

  1. Arahkan kursor ke nama fungsi, tekan gd — kalian langsung melompat ke definisinya. Tekan Ctrl+o untuk kembali ke posisi semula (jump list, episode 3).
  2. Tekan K pada sebuah fungsi — muncul jendela dokumentasi berisi signature dan docstring, tanpa membuka browser.
  3. Tekan <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.
  4. Tekan <leader>ca saat ada garis merah — muncul daftar quick fix seperti "import symbol ini" atau "auto-fix typo".

Workflow LSP dalam Satu Sesi Menulis Kode

Sekarang mari kita rangkai semua keymap LSP menjadi satu alur kerja yang realistis — menulis fungsi baru di project TypeScript:

Sesi menulis kode dengan LSP
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.

Konfigurasi Per Bahasa: TypeScript, Python, Go

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:

  • tsserver menggunakan 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.
  • pyright butuh settings.python.analysis untuk mengontrol seberapa agresif type checking-nya. basic adalah titik tengah yang baik antara akurasi dan noise.
  • gopls mendukung staticcheck — static analysis engine yang menangkap bug yang tidak dideteksi compiler. Aktifkan selalu untuk codebase Go.

Kesalahan Umum Seputar LSP & Mason

KesalahanGejalaSolusi
Server tidak terinstallError client X not available atau spawn ... failedInstall via :MasonInstall <server>; pastikan nama server di ensure_installed benar
Server attach tapi fitur matigd/gr tidak melakukan apa-apaCek :LspInfo; pastikan capabilities & on_attach dikirim lewat handler
Completion tidak munculPopup autocomplete LSP kosongFitur ini butuh nvim-cmp (episode 16); di episode ini capabilities sudah disiapkan
Diagnostics hanya muncul setelah disimpanError tidak muncul real-time saat mengetikNormal — banyak server publikasikan diagnosa saat file berubah; pastikan tidak ada yang meng-override
pyright vs basedpyright kebingunganPackage tidak ditemukan di MasonDi Mason nama packagenya basedpyright untuk versi fork; nama pyright adalah yang resmi
Lupa snippetSupportSnippet dari LSP tidak pernah munculPastikan capabilities.textDocument.completion.completionItem.snippetSupport = true (wajib sebelum nvim-cmp)
lua_ls tidak mengenal configvim.opt dan vim.api muncul sebagai undefinedlua_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.

Penutup

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!

Belajar Neovim - Built-in LSP (Language Server Protocol) & mason.nvim | Belajar Neovim