Tutorial de Language Server Protocol: de VSCode a Vim
Publicado: 2022-03-11Lo más probable es que el artefacto principal de todo su trabajo sean los archivos de texto sin formato. Entonces, ¿por qué no usas el Bloc de notas para crearlos?
El resaltado de sintaxis y el formato automático son solo la punta del iceberg. ¿Qué pasa con el linting, la finalización del código y la refactorización semiautomática? Todas estas son muy buenas razones para usar un editor de código "real". Estos son vitales en nuestro día a día, pero ¿entendemos cómo funcionan?
En este tutorial de Language Server Protocol, exploraremos un poco estas preguntas y descubriremos qué es lo que hace funcionar a nuestros editores de texto. Al final, juntos implementaremos un servidor de idioma básico junto con clientes de ejemplo para VSCode, Sublime Text 3 y Vim.
Compiladores frente a servicios lingüísticos
Omitiremos el resaltado de sintaxis y el formato por ahora, que se maneja con análisis estático, un tema interesante por derecho propio, y nos centraremos en los principales comentarios que recibimos de estas herramientas. Hay dos categorías principales: compiladores y servicios de lenguaje.
Los compiladores toman su código fuente y escupen una forma diferente. Si el código no sigue las reglas del lenguaje, el compilador devolverá errores. Estos son bastante familiares. El problema con esto es que suele ser bastante lento y de alcance limitado. ¿Qué hay de ofrecer asistencia mientras todavía está creando el código?
Esto es lo que ofrecen los servicios lingüísticos. Pueden brindarle información sobre su base de código mientras aún está en proceso, y probablemente mucho más rápido que compilar todo el proyecto.
El alcance de estos servicios es variado. Puede ser algo tan simple como devolver una lista de todos los símbolos del proyecto, o algo complejo como devolver los pasos para refactorizar el código. Estos servicios son la razón principal por la que usamos nuestros editores de código. Si solo quisiéramos compilar y ver errores, podríamos hacerlo con unas pocas pulsaciones de teclas. Los servicios lingüísticos nos brindan más información y muy rápidamente.
Apostando por un Editor de Texto para Programación
Tenga en cuenta que aún no hemos mencionado editores de texto específicos. Expliquemos por qué con un ejemplo.
Digamos que ha desarrollado un nuevo lenguaje de programación llamado Lapine. Es un lenguaje hermoso y el compilador da excelentes mensajes de error tipo Elm. Además, puede proporcionar finalización de código, referencias, ayuda de refactorización y diagnósticos.
¿Qué código/editor de texto admite primero? ¿Qué pasa después de eso? Tienes una batalla cuesta arriba luchando para lograr que la gente lo adopte, así que quieres que sea lo más fácil posible. No desea elegir el editor equivocado y perder usuarios. ¿Qué sucede si se mantiene alejado de los editores de código y se enfoca en su especialidad: el lenguaje y sus funciones?
Servidores de idiomas
Introduzca los servidores de idioma . Estas son herramientas que hablan con los clientes de idiomas y brindan los conocimientos que hemos mencionado. Son independientes de los editores de texto por las razones que acabamos de describir con nuestra situación hipotética.
Como de costumbre, otra capa de abstracción es justo lo que necesitamos. Estos prometen romper el estrecho vínculo entre las herramientas de lenguaje y los editores de código. Los creadores de lenguajes pueden incluir sus características en un servidor una vez, y los editores de código/texto pueden agregar pequeñas extensiones para convertirse en clientes. Es una victoria para todos. Sin embargo, para facilitar esto, debemos acordar cómo se comunicarán estos clientes y servidores.
Por suerte para nosotros, esto no es hipotético. Microsoft ya ha comenzado definiendo el Protocolo de Servidor de Idiomas.
Como ocurre con la mayoría de las grandes ideas, surgió de la necesidad más que de la previsión. Muchos editores de código ya habían comenzado a agregar soporte para varias funciones de lenguaje; algunas características subcontratadas a herramientas de terceros, algunas hechas bajo el capó dentro de los editores. Surgieron problemas de escalabilidad y Microsoft tomó la iniciativa de dividir las cosas. Sí, Microsoft allanó el camino para sacar estas características de los editores de código en lugar de acumularlas dentro de VSCode. Podrían haber seguido construyendo su editor, bloqueando a los usuarios, pero los liberaron.
Protocolo de servidor de idioma
El Protocolo de servidor de idiomas (LSP) se definió en 2016 para ayudar a separar las herramientas y los editores de idiomas. Todavía hay muchas huellas dactilares de VSCode, pero es un paso importante en la dirección del agnosticismo del editor. Examinemos un poco el protocolo.
Clientes y servidores (piense en editores de código y herramientas de lenguaje) se comunican en mensajes de texto simples. Estos mensajes tienen encabezados similares a HTTP, contenido JSON-RPC y pueden originarse en el cliente o en el servidor. El protocolo JSON-RPC define solicitudes, respuestas y notificaciones y algunas reglas básicas en torno a ellas. Una característica clave es que está diseñado para funcionar de forma asíncrona, por lo que los clientes/servidores pueden tratar los mensajes desordenados y con cierto grado de paralelismo.
En resumen, JSON-RPC permite que un cliente solicite a otro programa que ejecute un método con parámetros y devuelva un resultado o un error. LSP se basa en esto y define los métodos disponibles, las estructuras de datos esperadas y algunas reglas más sobre las transacciones. Por ejemplo, hay un proceso de negociación cuando el cliente inicia el servidor.
El servidor tiene estado y solo está diseñado para manejar un solo cliente a la vez. Sin embargo, no hay restricciones explícitas en la comunicación, por lo que un servidor de idioma podría ejecutarse en una máquina diferente a la del cliente. Sin embargo, en la práctica, eso sería bastante lento para la retroalimentación en tiempo real. Los servidores de idiomas y los clientes trabajan con los mismos archivos y son bastante comunicativos.
El LSP tiene una cantidad decente de documentación una vez que sabe qué buscar. Como se mencionó, mucho de esto está escrito dentro del contexto de VSCode, aunque las ideas tienen una aplicación mucho más amplia. Por ejemplo, la especificación del protocolo está escrita en TypeScript. Para ayudar a los exploradores que no están familiarizados con VSCode y TypeScript, aquí hay una introducción.
Tipos de mensajes LSP
Hay muchos grupos de mensajes definidos en el Protocolo de Servidor de Idiomas. Se pueden dividir aproximadamente en "administrador" y "características de idioma". Los mensajes de administración contienen los que se utilizan en el protocolo de enlace cliente/servidor, apertura/alteración de archivos, etc. Es importante destacar que aquí es donde los clientes y servidores comparten las funciones que manejan. Ciertamente, diferentes lenguajes y herramientas ofrecen diferentes funciones. Esto también permite la adopción incremental. Langserver.org nombra media docena de funciones clave que los clientes y servidores deben admitir, al menos una de las cuales es necesaria para hacer la lista.
Las características del idioma son lo que más nos interesa. De estas, hay una que se debe mencionar específicamente: el mensaje de diagnóstico. Los diagnósticos son una de las características clave. Cuando abre un archivo, se supone principalmente que se ejecutará. Su editor debe informarle si hay algún problema con el archivo. La forma en que esto sucede con LSP es:
- El cliente abre el archivo y envía
textDocument/didOpen
al servidor. - El servidor analiza el archivo y envía la notificación
textDocument/publishDiagnostics
. - El cliente analiza los resultados y muestra indicadores de error en el editor.
Esta es una forma pasiva de obtener información de sus servicios lingüísticos. Un ejemplo más activo sería encontrar todas las referencias del símbolo debajo del cursor. Esto sería algo como:
- El cliente envía
textDocument/references
al servidor, especificando una ubicación en un archivo. - El servidor descubre el símbolo, localiza las referencias en este y otros archivos y responde con una lista.
- El cliente muestra las referencias al usuario.
Una herramienta de lista negra
Seguramente podríamos profundizar en los detalles del Protocolo de servidor de idiomas, pero dejemos eso para los implementadores de clientes. Para cimentar la idea de la separación de la herramienta del editor y el idioma, jugaremos el papel de creador de la herramienta.
Lo mantendremos simple y, en lugar de crear un nuevo idioma y funciones, nos ceñiremos a los diagnósticos. Los diagnósticos encajan bien: son solo advertencias sobre el contenido de un archivo. Un linter devuelve diagnósticos. Haremos algo parecido.
Crearemos una herramienta para que nos notifique las palabras que nos gustaría evitar. Luego, proporcionaremos esa funcionalidad a un par de editores de texto diferentes.
El servidor de idiomas
Primero, la herramienta. Hornearemos esto directamente en un servidor de idioma. Para simplificar, esta será una aplicación de Node.js, aunque podríamos hacerlo con cualquier tecnología capaz de usar secuencias para leer y escribir.
Aquí está la lógica. Dado un texto, este método devuelve una matriz de las palabras coincidentes en la lista negra y los índices donde se encontraron.
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 }
Ahora, hagámoslo un servidor.
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()
Aquí, estamos utilizando vscode-languageserver
. El nombre es engañoso, ya que ciertamente puede funcionar fuera de VSCode. Esta es una de las muchas "huellas dactilares" que ves de los orígenes de LSP. vscode-languageserver
se encarga del protocolo de nivel inferior y le permite concentrarse en los casos de uso. Este fragmento inicia una conexión y la vincula a un administrador de documentos. Cuando un cliente se conecta al servidor, el servidor le indicará que le gustaría recibir una notificación sobre la apertura de documentos de texto.

Podríamos parar aquí. Este es un servidor LSP completamente funcional, aunque inútil. En su lugar, respondamos a los cambios del documento con alguna información de diagnóstico.
documents.onDidChangeContent(change => { connection.sendDiagnostics({ uri: change.document.uri, diagnostics: getDiagnostics(change.document), }) })
Finalmente, conectamos los puntos entre el documento que cambió, nuestra lógica y la respuesta de diagnóstico.
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', })
Nuestra carga útil de diagnóstico será el resultado de ejecutar el texto del documento a través de nuestra función y luego asignarlo al formato esperado por el cliente.
Este script creará todo eso para ti.
curl -o- https://raw.githubusercontent.com/reergymerej/lsp-article-resources/revision-for-6.0.0/blacklist-server-install.sh | bash
Nota: si no se siente cómodo con extraños que agregan ejecutables a su máquina, verifique la fuente. Crea el proyecto, descarga index.js
y npm link
por usted.
Fuente de servidor completa
La fuente final blacklist-server
es:
#!/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 del protocolo del servidor de idiomas: tiempo para una prueba de manejo
Después de link
el proyecto, intente ejecutar el servidor, especificando stdio
como mecanismo de transporte:
blacklist-server --stdio
Está escuchando en stdio
ahora los mensajes LSP de los que hablamos antes. Podríamos proporcionarlos manualmente, pero vamos a crear un cliente en su lugar.
Cliente de idioma: VSCode
Como esta tecnología se originó en VSCode, parece apropiado comenzar allí. Crearemos una extensión que creará un cliente LSP y lo conectará al servidor que acabamos de crear.
Hay varias formas de crear una extensión VSCode, incluido el uso de Yeoman y el generador apropiado, generator-code
. Sin embargo, para simplificar, hagamos un ejemplo básico.
Clonemos el repetitivo e instalemos sus dependencias:
git clone [email protected]:reergymerej/standalone-vscode-ext.git blacklist-vscode cd blacklist-vscode npm i # or yarn
Abra el directorio blacklist-vscode
en VSCode.
Presione F5 para iniciar otra instancia de VSCode, depurando la extensión.
En la "consola de depuración" de la primera instancia de VSCode, verá el texto "Mira, ma. ¡Una extensión!"
Ahora tenemos una extensión VSCode básica que funciona sin todas las campanas y silbatos. Hagámoslo un cliente LSP. Cierre ambas instancias de VSCode y desde el directorio blacklist-vscode
, ejecute:
npm i vscode-languageclient
Reemplace extensión.js con:
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()) }, }
Esto usa el paquete vscode-languageclient
para crear un cliente LSP dentro de VSCode. A diferencia vscode-languageserver
, esto está estrechamente relacionado con VSCode. En resumen, lo que estamos haciendo en esta extensión es crear un cliente y decirle que use el servidor que creamos en los pasos anteriores. Pasando por alto los detalles de la extensión VSCode, podemos ver que le estamos diciendo que use este cliente LSP para archivos de texto sin formato.
Para probarlo, abra el directorio blacklist-vscode
en VSCode. Presione F5 para iniciar otra instancia, depurando la extensión.
En la nueva instancia de VSCode, cree un archivo de texto sin formato y guárdelo. Escribe "foo" o "bar" y espera un momento. Verá advertencias de que están en la lista negra.
¡Eso es todo! No tuvimos que recrear nada de nuestra lógica, solo coordinar el cliente y el servidor.
Hagámoslo nuevamente para otro editor, esta vez Sublime Text 3. El proceso será bastante similar y un poco más fácil.
Cliente de idiomas: texto sublime 3
Primero, abra ST3 y abra la paleta de comandos. Necesitamos un marco para hacer del editor un cliente LSP. Escriba "Control de paquete: Instalar paquete" y presione enter. Busque el paquete "LSP" e instálelo. Una vez completado, tenemos la capacidad de especificar clientes LSP. Hay muchos ajustes preestablecidos, pero no los vamos a usar. Hemos creado el nuestro.
Nuevamente, abra la paleta de comandos. Busque "Preferencias: Configuración de LSP" y presione enter. Esto abrirá el archivo de configuración, LSP.sublime-settings
, para el paquete LSP. Para agregar un cliente personalizado, use la configuración a continuación.
{ "clients": { "blacklister": { "command": [ "blacklist-server", "--stdio" ], "enabled": true, "languages": [ { "syntaxes": [ "Plain text" ] } ] } }, "log_debug": true }
Esto puede parecer familiar por la extensión VSCode. Definimos un cliente, le dijimos que trabajara en archivos de texto sin formato y especificamos el servidor de idioma.
Guarde la configuración, luego cree y guarde un archivo de texto sin formato. Escribe "foo" o "bar" y espera. Nuevamente, verá advertencias de que están en la lista negra. El tratamiento, cómo se muestran los mensajes en el editor, es diferente. Sin embargo, nuestra funcionalidad es la misma. Apenas hicimos nada esta vez para agregar soporte al editor.
Idioma “Cliente”: Vim
Si aún no está convencido de que esta separación de preocupaciones facilita compartir funciones entre editores de texto, estos son los pasos para agregar la misma funcionalidad a Vim a través de Coc.
Abra Vim y escriba :CocConfig
, luego agregue:
"languageserver": { "blacklister": { "command": "blacklist-server", "args": ["--stdio"], "filetypes": ["text"] } }
Hecho.
La separación cliente-servidor permite que los idiomas y los servicios lingüísticos prosperen
Separar la responsabilidad de los servicios lingüísticos de los editores de texto en los que se utilizan es claramente una victoria. Permite a los creadores de funciones de idiomas centrarse en su especialidad y a los creadores de editores hacer lo mismo. Es una idea bastante nueva, pero la adopción se está extendiendo.
Ahora que tiene una base para trabajar, tal vez pueda encontrar un proyecto y ayudar a hacer avanzar esta idea. La guerra de los editores nunca terminará, pero está bien. Siempre que las habilidades lingüísticas puedan existir fuera de editores específicos, puede usar cualquier editor que desee.
Como Microsoft Gold Partner, Toptal es su red élite de expertos de Microsoft. Cree equipos de alto rendimiento con los expertos que necesita, ¡en cualquier lugar y exactamente cuando los necesite!