Tutorial Protokol Server Bahasa: Dari VSCode ke Vim
Diterbitkan: 2022-03-11Artefak utama dari semua pekerjaan Anda kemungkinan besar adalah file teks biasa. Jadi mengapa Anda tidak menggunakan Notepad untuk membuatnya?
Penyorotan sintaks dan pemformatan otomatis hanyalah puncak gunung es. Bagaimana dengan linting, penyelesaian kode, dan refactoring semi-otomatis? Ini semua adalah alasan yang sangat bagus untuk menggunakan editor kode "nyata". Ini sangat penting untuk kehidupan kita sehari-hari, tetapi apakah kita memahami cara kerjanya?
Dalam tutorial Protokol Server Bahasa ini, kita akan sedikit mengeksplorasi pertanyaan-pertanyaan ini dan mencari tahu apa yang membuat editor teks kita tergerak. Pada akhirnya, bersama-sama kita akan mengimplementasikan server bahasa dasar bersama dengan klien contoh untuk VSCode, Sublime Text 3, dan Vim.
Kompilator vs. Layanan Bahasa
Kami akan melewatkan penyorotan dan pemformatan sintaks untuk saat ini, yang ditangani dengan analisis statis—topik yang menarik dalam dirinya sendiri—dan fokus pada umpan balik utama yang kami dapatkan dari alat ini. Ada dua kategori utama: kompiler dan layanan bahasa.
Kompiler mengambil kode sumber Anda dan mengeluarkan bentuk yang berbeda. Jika kode tidak mengikuti aturan bahasa, kompiler akan mengembalikan kesalahan. Ini cukup akrab. Masalah dengan ini biasanya cukup lambat dan cakupannya terbatas. Bagaimana dengan menawarkan bantuan saat Anda masih membuat kode?
Inilah yang disediakan oleh layanan bahasa. Mereka dapat memberi Anda wawasan tentang basis kode Anda saat masih dalam pengerjaan, dan mungkin jauh lebih cepat daripada mengkompilasi seluruh proyek.
Cakupan layanan ini bervariasi. Ini bisa berupa sesuatu yang sederhana seperti mengembalikan daftar semua simbol dalam proyek, atau sesuatu yang rumit seperti mengembalikan langkah-langkah ke kode refactor. Layanan ini adalah alasan utama kami menggunakan editor kode kami. Jika kita hanya ingin mengkompilasi dan melihat kesalahan, kita dapat melakukannya dengan beberapa penekanan tombol. Layanan bahasa memberi kami lebih banyak wawasan, dan sangat cepat.
Bertaruh pada Editor Teks untuk Pemrograman
Perhatikan bahwa kami belum memanggil editor teks tertentu. Mari kita jelaskan mengapa dengan sebuah contoh.
Katakanlah Anda telah mengembangkan bahasa pemrograman baru yang disebut Lapine. Ini adalah bahasa yang indah dan kompilernya memberikan pesan kesalahan seperti Elm yang hebat. Selain itu, Anda dapat memberikan penyelesaian kode, referensi, bantuan refactoring, dan diagnostik.
Kode/editor teks mana yang Anda dukung terlebih dahulu? Bagaimana setelah itu? Anda harus berjuang keras untuk membuat orang mengadopsinya, jadi Anda ingin membuatnya semudah mungkin. Anda tidak ingin memilih editor yang salah dan kehilangan pengguna. Bagaimana jika Anda menjaga jarak dari editor kode dan fokus pada spesialisasi Anda—bahasa dan fitur-fiturnya?
Server Bahasa
Masukkan server bahasa . Ini adalah alat yang berbicara dengan klien bahasa dan memberikan wawasan yang telah kami sebutkan. Mereka independen dari editor teks untuk alasan yang baru saja kami jelaskan dengan situasi hipotetis kami.
Seperti biasa, lapisan abstraksi lain adalah yang kita butuhkan. Ini menjanjikan untuk mematahkan hubungan erat antara alat bahasa dan editor kode. Pembuat bahasa dapat membungkus fitur mereka di server sekali, dan editor kode/teks dapat menambahkan ekstensi kecil untuk mengubah diri mereka menjadi klien. Ini adalah kemenangan untuk semua orang. Namun, untuk memfasilitasi ini, kita perlu menyepakati bagaimana klien dan server ini akan berkomunikasi.
Beruntung bagi kami, ini bukan hipotetis. Microsoft telah memulai dengan mendefinisikan Language Server Protocol.
Seperti kebanyakan ide hebat, ide itu tumbuh karena kebutuhan dan bukan pandangan ke depan. Banyak editor kode sudah mulai menambahkan dukungan untuk berbagai fitur bahasa; beberapa fitur dialihdayakan ke alat pihak ketiga, beberapa dilakukan di bawah tenda di dalam editor. Masalah skalabilitas muncul, dan Microsoft memimpin dalam pemecahan masalah. Ya, Microsoft membuka jalan untuk memindahkan fitur-fitur ini dari editor kode daripada menyimpannya di dalam VSCode. Mereka bisa saja terus membangun editor mereka, mengunci pengguna—tetapi mereka membebaskan mereka.
Protokol Server Bahasa
Language Server Protocol (LSP) ditetapkan pada tahun 2016 untuk membantu memisahkan alat bahasa dan editor. Masih ada banyak sidik jari VSCode di dalamnya, tetapi ini adalah langkah besar menuju agnostisisme editor. Mari kita periksa protokolnya sedikit.
Klien dan server—pikirkan editor kode dan alat bahasa—berkomunikasi dalam pesan teks sederhana. Pesan-pesan ini memiliki header seperti HTTP, konten JSON-RPC, dan mungkin berasal dari klien atau server. Protokol JSON-RPC mendefinisikan permintaan, tanggapan, dan pemberitahuan serta beberapa aturan dasar di sekitarnya. Fitur utamanya adalah ia dirancang untuk bekerja secara asinkron, sehingga klien/server dapat menangani pesan yang tidak berurutan dan dengan tingkat paralelisme.
Singkatnya, JSON-RPC memungkinkan klien untuk meminta program lain untuk menjalankan metode dengan parameter dan mengembalikan hasil atau kesalahan. LSP membangun ini dan mendefinisikan metode yang tersedia, struktur data yang diharapkan, dan beberapa aturan lagi seputar transaksi. Misalnya, ada proses jabat tangan saat klien memulai server.
Server stateful dan hanya dimaksudkan untuk menangani satu klien pada satu waktu. Namun, tidak ada batasan eksplisit pada komunikasi, sehingga server bahasa dapat berjalan di mesin yang berbeda dari klien. Dalam praktiknya, itu akan sangat lambat untuk umpan balik waktu nyata. Server bahasa dan klien bekerja dengan file yang sama dan cukup cerewet.
LSP memiliki jumlah dokumentasi yang layak setelah Anda tahu apa yang harus dicari. Seperti yang disebutkan, banyak dari ini ditulis dalam konteks VSCode, meskipun ide-idenya memiliki aplikasi yang jauh lebih luas. Misalnya, spesifikasi protokol semuanya ditulis dalam TypeScript. Untuk membantu penjelajah yang tidak terbiasa dengan VSCode dan TypeScript, inilah primernya.
Jenis Pesan LSP
Ada banyak grup pesan yang didefinisikan dalam Language Server Protocol. Mereka secara kasar dapat dibagi menjadi "admin" dan "fitur bahasa." Pesan admin berisi pesan yang digunakan dalam jabat tangan klien/server, membuka/mengubah file, dll. Yang penting, ini adalah tempat klien dan server berbagi fitur mana yang mereka tangani. Tentu saja, bahasa dan alat yang berbeda menawarkan fitur yang berbeda. Ini juga memungkinkan adopsi bertahap. Langserver.org menyebutkan setengah lusin fitur utama yang harus didukung oleh klien dan server, setidaknya satu di antaranya diperlukan untuk membuat daftar.
Fitur bahasa adalah hal yang paling kami minati. Dari fitur tersebut, ada satu yang perlu disebutkan secara khusus: pesan diagnostik. Diagnostik adalah salah satu fitur utama. Saat Anda membuka file, sebagian besar diasumsikan bahwa ini akan berjalan. Editor Anda harus memberi tahu Anda jika ada yang salah dengan file tersebut. Cara ini terjadi dengan LSP adalah:
- Klien membuka file dan mengirim
textDocument/didOpen
ke server. - Server menganalisis file dan mengirimkan pemberitahuan
textDocument/publishDiagnostics
. - Klien mem-parsing hasil dan menampilkan indikator kesalahan di editor.
Ini adalah cara pasif untuk mendapatkan wawasan dari layanan bahasa Anda. Contoh yang lebih aktif adalah menemukan semua referensi untuk simbol di bawah kursor Anda. Ini akan berjalan seperti:
- Klien mengirimkan
textDocument/references
ke server, menentukan lokasi dalam file. - Server mencari tahu simbol, menempatkan referensi di file ini dan file lainnya, dan merespons dengan daftar.
- Klien menampilkan referensi kepada pengguna.
Alat Daftar Hitam
Kita pasti bisa menggali secara spesifik dari Language Server Protocol, tapi mari kita biarkan itu untuk pelaksana klien. Untuk memperkuat ide pemisahan editor dan alat bahasa, kami akan memainkan peran sebagai pencipta alat.
Kami akan membuatnya tetap sederhana dan, alih-alih membuat bahasa dan fitur baru, kami akan tetap menggunakan diagnostik. Diagnostik sangat cocok: Itu hanya peringatan tentang konten file. Sebuah linter mengembalikan diagnostik. Kami akan membuat sesuatu yang serupa.
Kami akan membuat alat untuk memberi tahu kami tentang kata-kata yang ingin kami hindari. Kemudian, kami akan menyediakan fungsionalitas itu ke beberapa editor teks yang berbeda.
Server Bahasa
Pertama, alatnya. Kami akan memanggang ini langsung ke server bahasa. Untuk kesederhanaan, ini akan menjadi aplikasi Node.js, meskipun kami dapat melakukannya dengan teknologi apa pun yang dapat menggunakan aliran untuk membaca dan menulis.
Berikut logikanya. Diberikan beberapa teks, metode ini mengembalikan larik kata-kata daftar hitam yang cocok dan indeks di mana kata-kata itu ditemukan.
const getBlacklisted = (text) => { const blacklist = [ 'foo', 'bar', 'baz', ] const regex = new RegExp(`\\b(${blacklist.join('|')})\\b`, 'gi') const results = [] while ((matches = regex.exec(text)) && results.length < 100) { results.push({ value: matches[0], index: matches.index, }) } return results }
Sekarang, mari kita membuatnya menjadi server.
const { TextDocuments, createConnection, } = require('vscode-languageserver') const {TextDocument} = require('vscode-languageserver-textdocument') const connection = createConnection() const documents = new TextDocuments(TextDocument) connection.onInitialize(() => ({ capabilities: { textDocumentSync: documents.syncKind, }, })) documents.listen(connection) connection.listen()
Di sini, kami menggunakan vscode-languageserver
. Namanya menyesatkan, karena pasti bisa bekerja di luar VSCode. Ini adalah salah satu dari banyak "sidik jari" yang Anda lihat tentang asal-usul LSP. vscode-languageserver
menangani protokol tingkat rendah dan memungkinkan Anda untuk fokus pada kasus penggunaan. Cuplikan ini memulai koneksi dan mengikatnya ke pengelola dokumen. Ketika klien terhubung ke server, server akan memberitahunya bahwa ia ingin diberitahu tentang dokumen teks yang sedang dibuka.

Kita bisa berhenti di sini. Ini adalah server LSP yang berfungsi penuh, meskipun tidak ada gunanya. Sebagai gantinya, mari kita tanggapi perubahan dokumen dengan beberapa informasi diagnostik.
documents.onDidChangeContent(change => { connection.sendDiagnostics({ uri: change.document.uri, diagnostics: getDiagnostics(change.document), }) })
Terakhir, kami menghubungkan titik-titik antara dokumen yang berubah, logika kami, dan respons diagnostik.
const getDiagnostics = (textDocument) => getBlacklisted(textDocument.getText()) .map(blacklistToDiagnostic(textDocument)) const { DiagnosticSeverity, } = require('vscode-languageserver') const blacklistToDiagnostic = (textDocument) => ({ index, value }) => ({ severity: DiagnosticSeverity.Warning, range: { start: textDocument.positionAt(index), end: textDocument.positionAt(index + value.length), }, message: `${value} is blacklisted.`, source: 'Blacklister', })
Payload diagnostik kami akan menjadi hasil menjalankan teks dokumen melalui fungsi kami, kemudian dipetakan ke format yang diharapkan oleh klien.
Script ini akan membuat semua itu untuk Anda.
curl -o- https://raw.githubusercontent.com/reergymerej/lsp-article-resources/revision-for-6.0.0/blacklist-server-install.sh | bash
Catatan: Jika Anda tidak nyaman dengan orang asing yang menambahkan executable ke mesin Anda, silakan periksa sumbernya. Itu membuat proyek, mengunduh index.js
, dan npm link
untuk Anda.
Sumber Server Lengkap
Sumber blacklist-server
terakhir adalah:
#!/usr/bin/env node const { DiagnosticSeverity, TextDocuments, createConnection, } = require('vscode-languageserver') const {TextDocument} = require('vscode-languageserver-textdocument') const getBlacklisted = (text) => { const blacklist = [ 'foo', 'bar', 'baz', ] const regex = new RegExp(`\\b(${blacklist.join('|')})\\b`, 'gi') const results = [] while ((matches = regex.exec(text)) && results.length < 100) { results.push({ value: matches[0], index: matches.index, }) } return results } const blacklistToDiagnostic = (textDocument) => ({ index, value }) => ({ severity: DiagnosticSeverity.Warning, range: { start: textDocument.positionAt(index), end: textDocument.positionAt(index + value.length), }, message: `${value} is blacklisted.`, source: 'Blacklister', }) const getDiagnostics = (textDocument) => getBlacklisted(textDocument.getText()) .map(blacklistToDiagnostic(textDocument)) const connection = createConnection() const documents = new TextDocuments(TextDocument) connection.onInitialize(() => ({ capabilities: { textDocumentSync: documents.syncKind, }, })) documents.onDidChangeContent(change => { connection.sendDiagnostics({ uri: change.document.uri, diagnostics: getDiagnostics(change.document), }) }) documents.listen(connection) connection.listen()
Tutorial Protokol Server Bahasa: Saatnya Test Drive
Setelah proyek link
, coba jalankan server, tentukan stdio
sebagai mekanisme transport:
blacklist-server --stdio
Ini mendengarkan di stdio
sekarang untuk pesan LSP yang kita bicarakan sebelumnya. Kami dapat menyediakannya secara manual, tetapi mari kita buat klien sebagai gantinya.
Klien Bahasa: VSCode
Karena teknologi ini berasal dari VSCode, tampaknya tepat untuk memulai dari sana. Kami akan membuat ekstensi yang akan membuat klien LSP dan menghubungkannya ke server yang baru saja kami buat.
Ada beberapa cara untuk membuat ekstensi VSCode, termasuk menggunakan Yeoman dan generator yang sesuai, generator-code
. Untuk kesederhanaan, mari kita lakukan contoh barebone.
Mari kita mengkloning boilerplate dan menginstal dependensinya:
git clone [email protected]:reergymerej/standalone-vscode-ext.git blacklist-vscode cd blacklist-vscode npm i # or yarn
Buka direktori blacklist-vscode
di VSCode.
Tekan F5 untuk memulai instance VSCode lain, men-debug ekstensi.
Dalam "konsol debug" instance VSCode pertama, Anda akan melihat teks, "Lihat, ma. Sebuah ekstensi!"
Kami sekarang memiliki ekstensi VSCode dasar yang berfungsi tanpa semua lonceng dan peluit. Mari kita membuatnya menjadi klien LSP. Tutup kedua instance VSCode dan dari dalam direktori blacklist-vscode
, jalankan:
npm i vscode-languageclient
Ganti extension.js dengan:
const { LanguageClient } = require('vscode-languageclient') module.exports = { activate(context) { const executable = { command: 'blacklist-server', args: ['--stdio'], } const serverOptions = { run: executable, debug: executable, } const clientOptions = { documentSelector: [{ scheme: 'file', language: 'plaintext', }], } const client = new LanguageClient( 'blacklist-extension-id', 'Blacklister', serverOptions, clientOptions ) context.subscriptions.push(client.start()) }, }
Ini menggunakan paket vscode-languageclient
untuk membuat klien LSP dalam VSCode. Tidak seperti vscode-languageserver
, ini digabungkan dengan erat ke VSCode. Singkatnya, apa yang kami lakukan dalam ekstensi ini adalah membuat klien dan memerintahkannya untuk menggunakan server yang kami buat di langkah sebelumnya. Dengan mengabaikan spesifikasi ekstensi VSCode, kita dapat melihat bahwa kita menyuruhnya menggunakan klien LSP ini untuk file teks biasa.
Untuk mengujinya, buka direktori blacklist-vscode
di VSCode. Tekan F5 untuk memulai instance lain, men-debug ekstensi.
Dalam instance VSCode baru, buat file teks biasa dan simpan. Ketik "foo" atau "bar" dan tunggu sebentar. Anda akan melihat peringatan bahwa ini masuk daftar hitam.
Itu dia! Kami tidak perlu membuat ulang logika kami, cukup mengoordinasikan klien dan server.
Mari kita lakukan lagi untuk editor lain, kali ini Sublime Text 3. Prosesnya akan sangat mirip dan sedikit lebih mudah.
Klien Bahasa: Teks Sublim 3
Pertama, buka ST3 dan buka palet perintah. Kami membutuhkan kerangka kerja untuk menjadikan editor sebagai klien LSP. Ketik "Kontrol Paket: Instal Paket" dan tekan enter. Temukan paket "LSP" dan instal. Setelah selesai, kami memiliki kemampuan untuk menentukan klien LSP. Ada banyak preset, tapi kami tidak akan menggunakannya. Kami telah membuat milik kami sendiri.
Sekali lagi, buka palet perintah. Temukan "Preferensi: Pengaturan LSP" dan tekan enter. Ini akan membuka file konfigurasi, LSP.sublime-settings
, untuk paket LSP. Untuk menambahkan klien khusus, gunakan konfigurasi di bawah ini.
{ "clients": { "blacklister": { "command": [ "blacklist-server", "--stdio" ], "enabled": true, "languages": [ { "syntaxes": [ "Plain text" ] } ] } }, "log_debug": true }
Ini mungkin terlihat familier dari ekstensi VSCode. Kami mendefinisikan klien, menyuruhnya bekerja pada file teks biasa, dan menentukan server bahasa.
Simpan pengaturan, lalu buat dan simpan file teks biasa. Ketik "foo" atau "bar" dan tunggu. Sekali lagi, Anda akan melihat peringatan bahwa ini masuk daftar hitam. Perlakuannya—bagaimana pesan ditampilkan di editor—berbeda. Namun, fungsi kami sama. Kami bahkan nyaris tidak melakukan apa pun kali ini untuk menambahkan dukungan kepada editor.
Bahasa "Klien": Vim
Jika Anda masih tidak yakin bahwa pemisahan masalah ini memudahkan untuk berbagi fitur di seluruh editor teks, berikut adalah langkah-langkah untuk menambahkan fungsionalitas yang sama ke Vim melalui Coc.
Buka Vim dan ketik :CocConfig
, lalu tambahkan:
"languageserver": { "blacklister": { "command": "blacklist-server", "args": ["--stdio"], "filetypes": ["text"] } }
Selesai.
Pemisahan Client-server Memungkinkan Bahasa dan Layanan Bahasa Berkembang
Memisahkan tanggung jawab layanan bahasa dari editor teks tempat mereka digunakan jelas merupakan suatu kemenangan. Ini memungkinkan pembuat fitur bahasa untuk fokus pada spesialisasi mereka dan pembuat editor untuk melakukan hal yang sama. Ini adalah ide yang cukup baru, tetapi adopsi sedang menyebar.
Sekarang setelah Anda memiliki dasar untuk bekerja, mungkin Anda dapat menemukan proyek dan membantu memajukan ide ini. Perang api editor tidak akan pernah berakhir, tapi tidak apa-apa. Selama kemampuan bahasa bisa ada di luar editor tertentu, Anda bebas menggunakan editor apa pun yang Anda suka.
Sebagai Mitra Emas Microsoft, Toptal adalah jaringan elit pakar Microsoft Anda. Bangun tim berkinerja tinggi dengan para ahli yang Anda butuhkan - di mana pun dan kapan pun Anda membutuhkannya!