Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
¿Qué es el protocolo de servidor de idioma?
Admitir características de edición enriquecidas como autocompletaciones de código fuente o Ir a definición para un lenguaje de programación en un editor o IDE suele ser muy difícil y lento. Normalmente, requiere escribir un modelo de dominio (un escáner, un analizador, un comprobador de tipos, un generador y más) en el lenguaje de programación del editor o del IDE. Por ejemplo, el complemento de Eclipse CDT, que proporciona compatibilidad con C/C++ en el IDE de Eclipse se escribe en Java, ya que el propio IDE de Eclipse está escrito en Java. Después de este enfoque, significaría implementar un modelo de dominio de C/C++ en TypeScript para Visual Studio Code y un modelo de dominio independiente en C# para Visual Studio.
La creación de modelos de dominio específicos del lenguaje también es mucho más fácil si una herramienta de desarrollo puede reutilizar las bibliotecas específicas del lenguaje existentes. Sin embargo, estas bibliotecas normalmente se implementan en el propio lenguaje de programación (por ejemplo, los buenos modelos de dominio de C/C++ se implementan en C/C++). La integración de una biblioteca de C/C++ en un editor escrito en TypeScript es técnicamente posible pero difícil de hacer.
Servidores de idioma
Otro enfoque consiste en ejecutar la biblioteca en su propio proceso y usar la comunicación entre procesos para comunicarse con ella. Los mensajes enviados hacia atrás y hacia delante forman un protocolo. El protocolo de servidor de lenguaje (LSP) es el producto de estandarizar los mensajes intercambiados entre una herramienta de desarrollo y un proceso de servidor de lenguaje. El uso de servidores de lenguaje o demonios no es una idea nueva o nueva. Los editores como Vim y Emacs han estado haciendo esto durante algún tiempo para proporcionar compatibilidad con la finalización automática semántica. El objetivo del LSP era simplificar este tipo de integraciones y proporcionar un marco útil para exponer las características del lenguaje a una variedad de herramientas.
Tener un protocolo común permite la integración de características del lenguaje de programación en una herramienta de desarrollo con problemas mínimos mediante la reutilización de una implementación existente del modelo de dominio del lenguaje. Un back-end del servidor de lenguaje podría escribirse en PHP, Python o Java y el LSP le permite integrarse fácilmente en una variedad de herramientas. El protocolo funciona en un nivel común de abstracción para que una herramienta pueda ofrecer servicios de lenguaje enriquecidos sin necesidad de comprender completamente los matices específicos del modelo de dominio subyacente.
Cómo comenzó el trabajo en el LSP
El LSP ha evolucionado con el tiempo y hoy está en la versión 3.0. Se inició cuando OmniSharp cogió el concepto de un servidor de lenguaje para proporcionar características de edición enriquecidas para C#. Inicialmente, OmniSharp usó el protocolo HTTP con una carga JSON y se ha integrado en varios editores, incluido Visual Studio Code.
Aproximadamente al mismo tiempo, Microsoft comenzó a trabajar en un servidor de lenguaje TypeScript, con la idea de admitir TypeScript en editores como Emacs y Sublime Text. En esta implementación, un editor se comunica a través de stdin/stdout con el proceso de servidor de TypeScript y usa una carga JSON inspirada en el protocolo del depurador V8 para solicitudes y respuestas. El servidor TypeScript se ha integrado en el complemento Sublime de TypeScript y VS Code para la edición enriquecida de TypeScript.
Después de haber integrado dos servidores de lenguaje diferentes, el equipo de VS Code comenzó a explorar un protocolo de servidor de lenguaje común para editores e IDE. Un protocolo común permite a un proveedor de idiomas crear un servidor de idioma único que los IDE pueden consumir. Un consumidor de servidor de idioma solo tiene que implementar el lado cliente del protocolo una vez. Esto da como resultado una situación de win-win para el proveedor de idioma y el consumidor de idioma.
El protocolo de servidor de lenguaje comenzó con el protocolo usado por el servidor TypeScript, expansándolo con más características de lenguaje inspiradas en la API del lenguaje VS Code. El protocolo está respaldado con JSON-RPC para la invocación remota debido a su simplicidad y bibliotecas existentes.
El equipo de VS Code ha creado un prototipo del protocolo mediante la implementación de varios servidores de lenguaje linter que responden a las solicitudes a lint (examen) de un archivo y devuelven un conjunto de advertencias y errores detectados. El objetivo era lintar un archivo a medida que el usuario edita en un documento, lo que significa que habrá muchas solicitudes de linting durante una sesión del editor. Tiene sentido mantener un servidor en funcionamiento para que no sea necesario iniciar un nuevo proceso de linting para cada edición de usuario. Se implementaron varios servidores linter, incluidas las extensiones ESLint y TSLint de VS Code. Estos dos servidores linter se implementan en TypeScript/JavaScript y se ejecutan en Node.js. Comparten una biblioteca que implementa la parte cliente y servidor del protocolo.
Funcionamiento del LSP
Un servidor de lenguaje se ejecuta en su propio proceso y herramientas como Visual Studio o VS Code se comunican con el servidor mediante el protocolo de lenguaje a través de JSON-RPC. Otra ventaja del servidor de lenguaje que funciona en un proceso dedicado es que se evitan problemas de rendimiento relacionados con un único modelo de proceso. El canal de transporte efectivo puede ser stdio, sockets, canalizaciones con nombre o IPC de Node si el cliente y el servidor están escritos en Node.js.
A continuación se muestra un ejemplo de cómo se comunica una herramienta y un servidor de idioma durante una sesión de edición rutinaria:
El usuario abre un archivo (denominado documento) en la herramienta: la herramienta notifica al servidor de idioma que un documento está abierto ("textDocument/didOpen"). A partir de ahora, la verdad sobre el contenido del documento ya no reside en el sistema de archivos, sino que la herramienta la guarda en memoria.
El usuario realiza modificaciones: la herramienta notifica al servidor sobre el cambio del documento ('textDocument/didChange') y el servidor de idioma actualiza la información semántica del programa. Como sucede, el servidor de idioma analiza esta información y notifica a la herramienta los errores y advertencias detectados ("textDocument/publishDiagnostics").
El usuario ejecuta "Ir a definición" en un símbolo del editor: la herramienta envía una solicitud "textDocument/definition" con dos parámetros: (1) el URI del documento y (2) la posición de texto desde donde se inició la solicitud Ir a definición al servidor. El servidor responde con el URI del documento y la posición de la definición del símbolo dentro del documento.
El usuario cierra el documento (archivo): se envía una notificación "textDocument/didClose" desde la herramienta, informando al servidor de idioma de que el documento ya no está en memoria y que el contenido actual está ahora actualizado en el sistema de archivos.
En este ejemplo se muestra cómo se comunica el protocolo con el servidor de idioma en el nivel de características del editor como "Ir a definición", "Buscar todas las referencias". Los tipos de datos usados por el protocolo son el editor o los "tipos de datos" del IDE, como el documento de texto abierto actualmente y la posición del cursor. Los tipos de datos no están en el nivel de un modelo de dominio de lenguaje de programación que normalmente proporcionaría árboles de sintaxis abstractos y símbolos del compilador (por ejemplo, tipos resueltos, espacios de nombres, ...). Esto simplifica significativamente el protocolo.
Ahora echemos un vistazo a la solicitud "textDocument/definition" con más detalle. A continuación se muestran las cargas que van entre la herramienta cliente y el servidor de lenguaje para la solicitud "Ir a definición" en un documento de C++.
Esta es la solicitud:
{
"jsonrpc": "2.0",
"id" : 1,
"method": "textDocument/definition",
"params": {
"textDocument": {
"uri": "file:///p%3A/mseng/VSCode/Playgrounds/cpp/use.cpp"
},
"position": {
"line": 3,
"character": 12
}
}
}
Esta es la respuesta:
{
"jsonrpc": "2.0",
"id": "1",
"result": {
"uri": "file:///p%3A/mseng/VSCode/Playgrounds/cpp/provide.cpp",
"range": {
"start": {
"line": 0,
"character": 4
},
"end": {
"line": 0,
"character": 11
}
}
}
}
En retrospectiva, describir los tipos de datos en el nivel del editor en lugar de en el nivel del modelo de lenguaje de programación es una de las razones para el éxito del protocolo de servidor de lenguaje. Es mucho más sencillo estandarizar un URI de documento de texto o una posición del cursor en comparación con la estandarización de un árbol de sintaxis abstracta y símbolos del compilador en diferentes lenguajes de programación.
Cuando un usuario trabaja con diferentes lenguajes, VS Code normalmente inicia un servidor de lenguaje para cada lenguaje de programación. En el ejemplo siguiente se muestra una sesión en la que el usuario trabaja en archivos Java y SASS.
Capabilities
No todos los servidores de lenguaje pueden admitir todas las características definidas por el protocolo. Por lo tanto, el cliente y el servidor anuncian su conjunto de características compatible a través de "funcionalidades". Por ejemplo, un servidor anuncia que puede controlar la solicitud "textDocument/definition", pero es posible que no controle la solicitud "workspace/symbol". Del mismo modo, los clientes pueden anunciar que pueden proporcionar notificaciones "acerca de guardar" antes de guardar un documento, de modo que un servidor pueda calcular ediciones textuales para dar formato automáticamente al documento editado.
Integración de un servidor de idioma
La integración real de un servidor de idioma en una herramienta determinada no está definida por el protocolo de servidor de idioma y se deja a los implementadores de herramientas. Algunas herramientas integran los servidores de lenguaje de forma genérica con una extensión que puede iniciarse y comunicarse con cualquier tipo de servidor de lenguaje. Otros, como VS Code, crean una extensión personalizada por servidor de lenguaje, de modo que una extensión todavía pueda proporcionar algunas características de lenguaje personalizado.
Para simplificar la implementación de servidores de lenguaje y clientes, hay bibliotecas o SDK para los elementos de cliente y servidor. Estas bibliotecas se proporcionan para diferentes idiomas. Por ejemplo, hay un módulo npm de cliente de lenguaje para facilitar la integración de un servidor de lenguaje en una extensión de VS Code y otro módulo npm de servidor de lenguaje para escribir un servidor de lenguaje mediante Node.js. Esta es la lista actual de bibliotecas de soporte técnico.
Uso del protocolo de servidor de lenguaje en Visual Studio
- Agregar una extensión de protocolo de servidor de lenguaje: obtenga información sobre cómo integrar un servidor de lenguaje en Visual Studio.