Agregar una extensión de protocolo de servidor de lenguaje
El Protocolo de servidor de lenguaje (LSP) es un protocolo común, en forma de JSON RPC v2.0, que se usa para proporcionar características del servicio de lenguaje a varios editores de código. Con el protocolo, los desarrolladores pueden escribir un único servidor de lenguaje para proporcionar características del servicio de lenguaje como IntelliSense, diagnóstico de errores, buscar todas las referencias, etc., en varios editores de código que admitan el LSP. Tradicionalmente, los servicios de lenguaje de Visual Studio se pueden agregar mediante archivos de gramática TextMate para proporcionar funcionalidades básicas, como el resaltado de sintaxis o la escritura de servicios de lenguaje personalizados que usan el conjunto completo de API de extensibilidad de Visual Studio para proporcionar datos más completos. Con la compatibilidad de Visual Studio con LSP, hay una tercera opción.
Para garantizar la mejor experiencia de usuario posible, considere la posibilidad de implementar también Language Configuration, que proporciona procesamiento local de muchas de las mismas operaciones y, por tanto, puede mejorar el rendimiento de muchas de las operaciones del editor específicas del lenguaje compatibles con el LSP.
Protocolo de servidor de idioma
En este artículo se describe cómo crear una extensión de Visual Studio que use un servidor de lenguaje basado en LSP. Se supone que ya ha desarrollado un servidor de lenguaje basado en LSP y solo quiere integrarlo en Visual Studio.
Para obtener compatibilidad con Visual Studio, los servidores de lenguaje pueden comunicarse con el cliente (Visual Studio) a través de cualquier mecanismo de transmisión basado en secuencias, por ejemplo:
- Flujos de entrada y salida estándar
- Tuberías con nombre
- Sockets (solo TCP)
La intención del LSP y la compatibilidad con él en Visual Studio es incorporar servicios de lenguaje que no forman parte del producto de Visual Studio. No está pensado para ampliar los servicios de lenguaje existentes (como C#) en Visual Studio. Para ampliar los idiomas existentes, consulte la guía de extensibilidad del servicio de lenguaje (por ejemplo, el "Roslyn" .NET Compiler Platform) o vea Ampliar los servicios de editor y lenguaje.
Para obtener más información sobre el propio protocolo, consulte la documentación aquí.
Para obtener más información sobre cómo crear un servidor de lenguaje de ejemplo o cómo integrar un servidor de lenguaje existente en Visual Studio Code, consulte la documentación aquí.
Características admitidas por el protocolo del servidor de lenguaje
En las tablas siguientes se muestran las características de LSP que se admiten en Visual Studio:
Mensaje | Tiene compatibilidad en Visual Studio |
---|---|
inicializar | Sí |
inicializado | Sí |
cierre | Sí |
salir | Sí |
$/cancelRequest | Sí |
window/showMessage | Sí |
window/showMessageRequest | Sí |
window/logMessage | Sí |
telemetry/event | |
client/registerCapability | |
client/unregisterCapability | |
workspace/didChangeConfiguration | Sí |
workspace/didChangeWatchedFiles | Sí |
área de trabajo/símbolo | Sí |
workspace/executeCommand | Sí |
workspace/applyEdit | Sí |
textDocument/publishDiagnostics | Sí |
textDocument/didOpen | Sí |
textDocument/didChange | Sí |
textDocument/willSave | |
textDocument/willSaveWaitUntil | |
textDocument/didSave | Sí |
textDocument/didClose | Sí |
textDocument/completion | Sí |
finalización/resolución | Sí |
textDocument/hover | Sí |
textDocument/signatureHelp | Sí |
textDocument/references | Sí |
textDocument/documentHighlight | Sí |
textDocument/documentSymbol | Sí |
textDocument/formatting | Sí |
textDocument/rangeFormatting | Sí |
textDocument/onTypeFormatting | |
textDocument/definition | Sí |
textDocument/codeAction | Sí |
textDocument/codeLens | |
codeLens/resolve | |
textDocument/documentLink | |
documentLink/resolve | |
textDocument/rename | Sí |
Comenzar
Nota
A partir de la versión 15.8 de Visual Studio 2017, la compatibilidad con common Language Server Protocol está integrada en Visual Studio. Si ha creado extensiones LSP utilizando la versión preliminar del Cliente de Servidor de Lenguaje VSIX, dejarán de funcionar una vez que actualice a la versión 15.8 o posterior. Tendrá que hacer lo siguiente para que las extensiones de LSP funcionen de nuevo:
Desinstale el Microsoft Visual Studio Language Server Protocol Preview VSIX.
A partir de la versión 15.8, cada vez que realice una actualización en Visual Studio, la versión preliminar de VSIX se detecta y quita automáticamente.
Actualice la referencia de Nuget a la versión más reciente no preliminar para los paquetes LSP .
Elimine la dependencia de Microsoft Visual Studio Language Server Protocol Preview VSIX en el manifiesto VSIX.
Asegúrese de que VSIX especifica Visual Studio 2017 versión 15.8 Preview 3 como límite inferior para el destino de instalación.
Reconstruir y redesplegar.
Creación de un proyecto VSIX
Para crear una extensión de servicio de lenguaje mediante un servidor de lenguaje basado en LSP, primero asegúrese de tener instalada la carga de trabajo de desarrollo de extensiones de Visual Studio para su instancia de Visual Studio.
A continuación, cree un nuevo proyecto VSIX; para ello, vaya a Archivo>Nuevo Proyecto>Visual C#>Extensibilidad>Proyecto VSIX:
Servidor de lenguaje e instalación de ejecución
De forma predeterminada, las extensiones creadas para admitir servidores de lenguaje basados en LSP en Visual Studio no contienen los propios servidores de lenguaje ni los entornos de ejecución necesarios para ejecutarlos. Los desarrolladores de extensiones son responsables de distribuir los servidores de lenguaje y los entornos de ejecución necesarios. Hay varias maneras de hacerlo:
- Los servidores de idioma se pueden incrustar en VSIX como archivos de contenido.
- Cree una MSI para instalar el servidor de idioma o los entornos de ejecución necesarios.
- Proporcione instrucciones en Marketplace que informen a los usuarios sobre cómo obtener entornos de ejecución y servidores de idiomas.
Archivos de gramática textMate
El LSP no incluye la especificación sobre cómo proporcionar coloración de texto para idiomas. Para proporcionar una coloración personalizada para los lenguajes en Visual Studio, los desarrolladores de extensiones pueden usar un archivo de gramática TextMate. Para agregar archivos personalizados de gramática o tema de TextMate, siga estos pasos:
Cree una carpeta denominada "Gramáticas" dentro de la extensión (o puede ser el nombre que elija).
Dentro de la carpeta Grammars, incluya cualquier archivo *.tmlanguage, *.plist, *.tmtheme, o *.json que desee para proporcionar coloración personalizada.
Sugerencia
Un archivo .tmtheme define cómo los ámbitos se asignan a las clasificaciones de Visual Studio (claves de color con nombre). Para obtener instrucciones, puede hacer referencia al archivo global .tmtheme en el directorio %ProgramFiles(x86)%\Microsoft Visual Studio\<version>\<SKU>\Common7\IDE\CommonExtensions\Microsoft\TextMate\Starterkit\Themesg.
Cree un archivo de .pkgdef y agregue una línea similar a esta:
[$RootKey$\TextMate\Repositories] "MyLang"="$PackageFolder$\Grammars"
Haga clic con el botón derecho en los archivos y seleccione Propiedades. Cambie la acción Compilar a Contenido y cambie la propiedad Incluir en VSIX a Verdadero.
Después de completar los pasos anteriores, se agrega una carpeta Grammars al directorio de instalación del paquete como un origen de repositorio denominado "MyLang" ("MyLang" es solo un nombre para la desambiguación y puede ser cualquier cadena única). Todas las gramáticas (archivos.tmlanguage) y archivos de tema (archivos.tmtheme) en este directorio se consideran potenciales y reemplazan las gramáticas integradas que vienen con TextMate. Si las extensiones del archivo de gramática coinciden con la extensión del archivo que se abre, TextMate intervendrá.
Creación de un cliente de lenguaje sencillo
Interfaz principal: ILanguageClient
Después de crear el proyecto VSIX, agregue los siguientes paquetes NuGet al proyecto:
Nota
Cuando se toma una dependencia del paquete NuGet después de completar los pasos anteriores, también se agregan los paquetes Newtonsoft.Json y StreamJsonRpc al proyecto. No actualizar estos paquetes a menos que esté seguro de que esas nuevas versiones se instalarán en la versión de Visual Studio que tiene como destino la extensión. Los ensamblados no se incluirán en VSIX; en su lugar, se recogerán en el directorio de instalación de Visual Studio. Si hace referencia a una versión más reciente de los ensamblados que lo que está instalado en el equipo de un usuario, la extensión no funcionará.
A continuación, puede crear una nueva clase que implemente la interfaz de ILanguageClient, que es la interfaz principal necesaria para los clientes de lenguaje que se conectan a un servidor de lenguaje basado en LSP.
A continuación se muestra un ejemplo:
namespace MockLanguageExtension
{
[ContentType("bar")]
[Export(typeof(ILanguageClient))]
public class BarLanguageClient : ILanguageClient
{
public string Name => "Bar Language Extension";
public IEnumerable<string> ConfigurationSections => null;
public object InitializationOptions => null;
public IEnumerable<string> FilesToWatch => null;
public event AsyncEventHandler<EventArgs> StartAsync;
public event AsyncEventHandler<EventArgs> StopAsync;
public async Task<Connection> ActivateAsync(CancellationToken token)
{
await Task.Yield();
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Server", @"MockLanguageServer.exe");
info.Arguments = "bar";
info.RedirectStandardInput = true;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
info.CreateNoWindow = true;
Process process = new Process();
process.StartInfo = info;
if (process.Start())
{
return new Connection(process.StandardOutput.BaseStream, process.StandardInput.BaseStream);
}
return null;
}
public async Task OnLoadedAsync()
{
await StartAsync.InvokeAsync(this, EventArgs.Empty);
}
public Task OnServerInitializeFailedAsync(Exception e)
{
return Task.CompletedTask;
}
public Task OnServerInitializedAsync()
{
return Task.CompletedTask;
}
}
}
Los métodos principales que se deben implementar son OnLoadedAsync y ActivateAsync. se llama OnLoadedAsync cuando Visual Studio ha cargado tu extensión y el servidor de lenguaje está listo para iniciarse. En este método, puede invocar el delegado StartAsync inmediatamente para indicar que se debe iniciar el servidor de lenguaje, o bien puede realizar lógica adicional e invocar StartAsync más adelante. Para activar el servidor de idioma, debe llamar a StartAsync en algún momento.
ActivateAsync es el método que finalmente se invoca llamando al delegado StartAsync. Contiene la lógica para iniciar el servidor de idioma y establecer la conexión con él. Se debe devolver un objeto de conexión que contiene secuencias para escribir en el servidor y leer desde el servidor. Las excepciones producidas aquí se detectan y se muestran al usuario a través de un mensaje de InfoBar en Visual Studio.
Activación
Una vez implementada la clase de cliente de lenguaje, deberá definir dos atributos para que defina cómo se cargará en Visual Studio y se activará:
[Export(typeof(ILanguageClient))]
[ContentType("bar")]
MEF
Visual Studio usa MEF (Managed Extensibility Framework) para administrar sus puntos de extensibilidad. El atributo export indica a Visual Studio que esta clase debe recogerse como punto de extensión y cargarse en el momento adecuado.
Para usar MEF, también debe definir MEF como activo en el manifiesto VSIX.
Abra el diseñador de manifiestos VSIX y vaya a la pestaña Activos.
Haga clic en Nuevo para crear un activo nuevo:
- Type: Microsoft.VisualStudio.MefComponent
- origen: un proyecto de la solución actual
- Project: [Su proyecto]
Definición de tipo de contenido
Actualmente, la única manera de cargar la extensión del servidor de lenguaje basado en LSP es por tipo de contenido de archivo. Es decir, al definir la clase cliente de lenguaje (que implementa ILanguageClient), deberá definir los tipos de archivos que, cuando se abran, hará que la extensión se cargue. Si no se abren archivos que coincidan con el tipo de contenido definido, la extensión no se cargará.
Esto se hace mediante la definición de una o varias clases de ContentTypeDefinition
:
namespace MockLanguageExtension
{
public class BarContentDefinition
{
[Export]
[Name("bar")]
[BaseDefinition(CodeRemoteContentDefinition.CodeRemoteContentTypeName)]
internal static ContentTypeDefinition BarContentTypeDefinition;
[Export]
[FileExtension(".bar")]
[ContentType("bar")]
internal static FileExtensionToContentTypeDefinition BarFileExtensionDefinition;
}
}
En el ejemplo anterior, se crea una definición de tipo de contenido para los archivos que terminan en la extensión de archivo .bar. La definición del tipo de contenido recibe el nombre "bar" y debe derivar de CodeRemoteContentTypeName.
Después de agregar una definición de tipo de contenido, puede definir cuándo cargar la extensión de cliente de lenguaje en la clase de cliente de lenguaje:
[ContentType("bar")]
[Export(typeof(ILanguageClient))]
public class BarLanguageClient : ILanguageClient
{
}
La adición de compatibilidad con servidores de lenguaje LSP no requiere que implemente su propio sistema de proyecto en Visual Studio. Los clientes pueden abrir un único archivo o una carpeta en Visual Studio para empezar a usar el servicio de lenguaje. De hecho, la compatibilidad con los servidores de lenguaje LSP está diseñada para funcionar solo en escenarios de archivos o carpetas abiertos. Si se implementa un sistema de proyecto personalizado, algunas características (como la configuración) no funcionarán.
Características avanzadas
Configuración
La compatibilidad con la configuración personalizada específica del servidor de idioma está disponible, pero todavía está en proceso de mejora. La configuración es específica de lo que admite el servidor de idioma y normalmente controla cómo emite el servidor de idioma los datos. Por ejemplo, un servidor de idioma podría tener una configuración para el número máximo de errores notificados. Los autores de extensiones definirían un valor predeterminado, que los usuarios pueden cambiar para proyectos específicos.
Siga estos pasos para agregar compatibilidad con la configuración a la extensión del servicio de lenguaje LSP:
Agregue un archivo JSON (por ejemplo, MockLanguageExtensionSettings.json) al proyecto que contenga la configuración y sus valores predeterminados. Por ejemplo:
{ "foo.maxNumberOfProblems": -1 }
Haga clic con el botón derecho en el archivo JSON y seleccione Propiedades. Cambie la acción Compilar a "Contenido" y la propiedad "Incluir en VSIX' a verdadero.
Implemente ConfigurationSections y devuelva la lista de prefijos para la configuración definida en el archivo JSON (en Visual Studio Code, esto se asignaría al nombre de la sección de configuración en package.json):
public IEnumerable<string> ConfigurationSections { get { yield return "foo"; } }
Agregue un archivo .pkgdef al proyecto (agregue un nuevo archivo de texto y cambie la extensión de archivo a .pkgdef). El archivo pkgdef debe contener esta información:
[$RootKey$\OpenFolder\Settings\VSWorkspaceSettings\[settings-name]] @="$PackageFolder$\[settings-file-name].json"
Ejemplo:
[$RootKey$\OpenFolder\Settings\VSWorkspaceSettings\MockLanguageExtension] @="$PackageFolder$\MockLanguageExtensionSettings.json"
Haga clic con el botón derecho en el archivo .pkgdef y seleccione Propiedades. Cambie la acción Compilar a Contenido y la propiedad Incluir en VSIX a verdadero.
Abra el archivo source.extension.vsixmanifest y agregue un recurso en la pestaña de Recurso:
- Tipo: Microsoft.VisualStudio.VsPackage
- origen: archivo en el sistema de archivos
- Ruta: [Ruta al archivo .pkgdef]
Edición por el usuario de la configuración de un área de trabajo
El usuario abre un área de trabajo que contiene los archivos que posee el servidor.
El usuario agrega un archivo en la carpeta .vs llamada VSWorkspaceSettings.json.
El usuario agrega una línea al archivo VSWorkspaceSettings.json para una configuración que proporciona el servidor. Por ejemplo:
{ "foo.maxNumberOfProblems": 10 }
Habilitación del seguimiento de diagnósticos
El seguimiento de diagnósticos se puede habilitar para emitir todos los mensajes entre el cliente y el servidor, lo que puede resultar útil al depurar problemas. Para habilitar el seguimiento de diagnóstico, haga lo siguiente:
- Abra o cree el archivo de configuración del área de trabajo VSWorkspaceSettings.json (consulte "Edición del usuario de la configuración de un área de trabajo").
- Agregue la siguiente línea en el archivo json de configuración:
{
"foo.trace.server": "Off"
}
Hay tres valores posibles para el nivel de detalle del seguimiento:
- "Desactivado": el seguimiento se desactivó completamente
- "Mensajes": el seguimiento está activado, pero solo se realiza el seguimiento del nombre del método y el identificador de respuesta.
- "Detallado": seguimiento activado; se realiza un seguimiento de todo el mensaje RPC.
Cuando se activa el seguimiento, el contenido se escribe en un archivo del directorio %temp%\VisualStudio\LSP. El registro sigue el formato de nomenclatura [LanguageClientName]-[Datetime Stamp].log. Actualmente, el seguimiento solo se puede habilitar para escenarios de carpeta abierta. Abrir un único archivo para activar un servidor de lenguaje no tiene compatibilidad con el seguimiento de diagnósticos.
Mensajes personalizados
Existen API para facilitar el paso de mensajes a y la recepción de mensajes del servidor de idioma que no forman parte del protocolo de servidor de idioma estándar. Para controlar mensajes personalizados, implemente la interfaz ILanguageClientCustomMessage2 en su clase de cliente de lenguaje. La biblioteca VS-StreamJsonRpc se utiliza para transmitir mensajes personalizados entre los clientes de lenguaje y los servidores de lenguaje. Dado que la extensión de cliente del lenguaje LSP es igual que cualquier otra extensión de Visual Studio, puede decidir agregar características adicionales (que no son compatibles con el LSP) a Visual Studio (con otras API de Visual Studio) en la extensión a través de mensajes personalizados.
Recepción de mensajes personalizados
Para recibir mensajes personalizados del servidor de idioma, implemente la propiedad [CustomMessageTarget]((/dotnet/api/microsoft.visualstudio.languageserver.client.ilanguageclientcustommessage.custommessagetarget) en ILanguageClientCustomMessage2 y devuelva un objeto que sepa cómo controlar los mensajes personalizados. Ejemplo siguiente:
(/dotnet/api/microsoft.visualstudio.languageserver.client.ilanguageclientcustommessage.custommessagetarget) propiedad en ILanguageClientCustomMessage2 y devuelve un objeto que sabe cómo controlar los mensajes personalizados. Ejemplo siguiente:
internal class MockCustomLanguageClient : MockLanguageClient, ILanguageClientCustomMessage2
{
private JsonRpc customMessageRpc;
public MockCustomLanguageClient() : base()
{
CustomMessageTarget = new CustomTarget();
}
public object CustomMessageTarget
{
get;
set;
}
public class CustomTarget
{
public void OnCustomNotification(JToken arg)
{
// Provide logic on what happens OnCustomNotification is called from the language server
}
public string OnCustomRequest(string test)
{
// Provide logic on what happens OnCustomRequest is called from the language server
}
}
}
Envío de mensajes personalizados
Para enviar mensajes personalizados al servidor de idioma, implemente el método AttachForCustomMessageAsync en ILanguageClientCustomMessage2. Este método se invoca cuando se inicia el servidor de idioma y está listo para recibir mensajes. Un objeto JsonRpc se pasa como parámetro, que luego puede mantener para enviar mensajes al servidor de lenguaje mediante API VS-StreamJsonRpc. Ejemplo siguiente:
internal class MockCustomLanguageClient : MockLanguageClient, ILanguageClientCustomMessage2
{
private JsonRpc customMessageRpc;
public MockCustomLanguageClient() : base()
{
CustomMessageTarget = new CustomTarget();
}
public async Task AttachForCustomMessageAsync(JsonRpc rpc)
{
await Task.Yield();
this.customMessageRpc = rpc;
}
public async Task SendServerCustomNotification(object arg)
{
await this.customMessageRpc.NotifyWithParameterObjectAsync("OnCustomNotification", arg);
}
public async Task<string> SendServerCustomMessage(string test)
{
return await this.customMessageRpc.InvokeAsync<string>("OnCustomRequest", test);
}
}
Capa central
A veces, un desarrollador de extensiones puede querer interceptar los mensajes LSP enviados y recibidos del servidor de idioma. Por ejemplo, un desarrollador de extensiones puede querer modificar el parámetro de mensaje enviado para un mensaje LSP determinado o modificar los resultados devueltos desde el servidor de lenguaje para una característica LSP (por ejemplo, finalizaciones). Cuando esto es necesario, los desarrolladores de extensiones pueden usar middleLayer API para interceptar mensajes LSP.
Para interceptar un mensaje determinado, cree una clase que implemente la interfaz ILanguageClientMiddleLayer. A continuación, implemente la interfaz ILanguageClientCustomMessage2 en la clase cliente de lenguaje y devuelva una instancia de su objeto en la propiedad MiddleLayer. Ejemplo siguiente:
public class MockLanguageClient : ILanguageClient, ILanguageClientCustomMessage2
{
public object MiddleLayer => DiagnosticsFilterMiddleLayer.Instance;
private class DiagnosticsFilterMiddleLayer : ILanguageClientMiddleLayer
{
internal readonly static DiagnosticsFilterMiddleLayer Instance = new DiagnosticsFilterMiddleLayer();
private DiagnosticsFilterMiddleLayer() { }
public bool CanHandle(string methodName)
{
return methodName == "textDocument/publishDiagnostics";
}
public async Task HandleNotificationAsync(string methodName, JToken methodParam, Func<JToken, Task> sendNotification)
{
if (methodName == "textDocument/publishDiagnostics")
{
var diagnosticsToFilter = (JArray)methodParam["diagnostics"];
// ony show diagnostics of severity 1 (error)
methodParam["diagnostics"] = new JArray(diagnosticsToFilter.Where(diagnostic => diagnostic.Value<int?>("severity") == 1));
}
await sendNotification(methodParam);
}
public async Task<JToken> HandleRequestAsync(string methodName, JToken methodParam, Func<JToken, Task<JToken>> sendRequest)
{
return await sendRequest(methodParam);
}
}
}
La característica de nivel intermedio todavía está en desarrollo y aún no es completa.
Extensión de servidor de lenguaje LSP de ejemplo
Para ver el código fuente de una extensión de ejemplo mediante la API de cliente LSP en Visual Studio, consulte VSSDK-Extensibility-Samples ejemplo LSP.
Preguntas más frecuentes
me gustaría crear un sistema de proyectos personalizado para complementar mi servidor de lenguaje LSP para proporcionar compatibilidad con características más enriquecida en Visual Studio, ¿cómo puedo hacerlo?
La compatibilidad con servidores de lenguaje basados en LSP en Visual Studio se basa en la característica de carpeta abierta y está diseñada para no requerir un sistema de proyecto personalizado. Puede crear su propio sistema de proyectos personalizado siguiendo las instrucciones aquí, pero es posible que algunas características, como la configuración, no funcionen. La lógica de inicialización predeterminada para los servidores de lenguaje LSP consiste en pasar la ubicación de la carpeta raíz de la carpeta que se está abriendo, por lo que si usa un sistema de proyecto personalizado, es posible que tenga que proporcionar lógica personalizada durante la inicialización para asegurarse de que el servidor de lenguaje pueda iniciarse correctamente.
¿Cómo se agrega compatibilidad con el depurador?
Proporcionaremos compatibilidad con el protocolo de depuración común en una versión futura.
Si ya hay instalado un servicio de lenguaje compatible con VS (por ejemplo, JavaScript), ¿puedo instalar una extensión de servidor de lenguaje LSP que ofrezca características adicionales (como linting)?
Sí, pero no todas las características funcionarán correctamente. El objetivo final de las extensiones de servidor de lenguaje LSP es habilitar servicios de lenguaje no compatibles de forma nativa con Visual Studio. Puede crear extensiones que ofrecen compatibilidad adicional con servidores de lenguaje LSP, pero algunas características (como IntelliSense) no serán una experiencia fluida. En general, se recomienda usar extensiones de servidor de lenguaje LSP para proporcionar nuevas experiencias de lenguaje, no extender las existentes.
¿Dónde debo publicar mi VSIX del servidor de lenguaje LSP completado?
Consulte las instrucciones del marketplace aquí.