Dela via


Lägga till ett Language Server Protocol-tillägg

Language Server Protocol (LSP) är ett vanligt protokoll, i form av JSON RPC v2.0, som används för att tillhandahålla språktjänstfunktioner till olika kodredigerare. Med hjälp av protokollet kan utvecklare skriva en enda språkserver för att tillhandahålla språktjänstfunktioner som IntelliSense, feldiagnostik, hitta alla referenser och så vidare till olika kodredigerare som stöder LSP. Traditionellt kan språktjänster i Visual Studio läggas till med hjälp av TextMate-grammatikfiler för att tillhandahålla grundläggande funktioner, till exempel syntaxmarkering eller genom att skriva anpassade språktjänster som använder den fullständiga uppsättningen Visual Studio-utöknings-API:er för att tillhandahålla mer omfattande data. Med Visual Studio-stöd för LSP finns det ett tredje alternativ.

språkserverprotokolltjänst i Visual Studio

För att säkerställa bästa möjliga användarupplevelse kan du även implementera Language Configuration, som tillhandahåller lokal bearbetning av många av samma åtgärder, och därför kan förbättra prestandan för många av de språkspecifika redigeringsåtgärder som stöds av LSP.

Language Server Protocol

språkserverprotokollimplementering

Den här artikeln beskriver hur du skapar ett Visual Studio-tillägg som använder en LSP-baserad språkserver. Det förutsätter att du redan har utvecklat en LSP-baserad språkserver och bara vill integrera den i Visual Studio.

För stöd i Visual Studio kan språkservrar kommunicera med klienten (Visual Studio) via alla strömbaserade överföringsmekanismer, till exempel:

  • Standardindata-/utdataströmmar
  • Namngivna rör
  • Sockets (endast TCP)

Avsikten med LSP och stöd för den i Visual Studio är att registrera språktjänster som inte ingår i Visual Studio-produkten. Det är inte avsett att utöka befintliga språktjänster (till exempel C#) i Visual Studio. Om du vill utöka befintliga språk kan du läsa språktjänstens utökningsguide (till exempel "Roslyn" .NET Compiler Platform) eller se Utöka redigerings- och språktjänsterna.

Mer information om själva protokollet finns i dokumentationen här.

Mer information om hur du skapar en exempelspråkserver eller hur du integrerar en befintlig språkserver i Visual Studio Code finns i dokumentationen här.

Funktioner som stöds av Language Server Protocol

Följande tabeller visar vilka LSP-funktioner som stöds i Visual Studio:

Meddelande Har stöd i Visual Studio
Initialisera Ja
initialiserats Ja
avstängning Ja
utgång Ja
$/cancelRequest Ja
window/showMessage Ja
fönster/visaMeddelandeBegäran Ja
fönster/loggmeddelande Ja
telemetri/händelse
client/registerCapability
client/unregisterCapability
workspace/didChangeConfiguration Ja
workspace/didChangeWatchedFiles Ja
arbetsyta/symbol Ja
arbetsyta/verkställKommando Ja
arbetsyta/appliceraRedigering Ja
dokument/text publiceraDiagnostik Ja
textDocument/didOpen Ja
textDocument/didChange Ja
textDocument/skaSparas
textDocument/willSaveWaitUntil
textDocument/didSave Ja
textdokument/stängt Ja
textDokument/slutförande Ja
slutföra/lösa Ja
textDokument/hovring Ja
textDokument/signaturHjälp Ja
textdokument/referenser Ja
Dokumentsida/dokumentmarkering Ja
textDocument/documentSymbol Ja
textDokument/formatering Ja
textdokument/intervallformatering Ja
textDocument/onTypeFormatting
textDocument/definition Ja
textDocument/codeAction Ja
textDocument/codeLens
codeLens/resolve
textDocument/documentLink
documentLink/lös
textDocument/rename Ja

Sätta igång

Not

Från och med Visual Studio 2017 version 15.8 är stöd för det gemensamma Language Server Protocol inbyggt i Visual Studio. Om du har skapat LSP-tillägg med hjälp av förhandsversionen Language Server Client VSIX version slutar de att fungera när du uppgraderar till version 15.8 eller senare. Du måste göra följande för att få LSP-tilläggen att fungera igen:

  1. Avinstallera Microsoft Visual Studio Language Server Protocol Preview VSIX.

    Från och med version 15.8 identifieras och tas förhandsversions-VSIX automatiskt bort varje gång du utför en uppgradering i Visual Studio.

  2. Uppdatera Nuget-referensen till den senaste versionen som inte är förhandsversion för LSP-paket.

  3. Ta bort beroendet till MICROSOFT Visual Studio Language Server Protocol Preview VSIX i VSIX-manifestet.

  4. Kontrollera att VSIX anger Visual Studio 2017 version 15.8 Förhandsversion 3 som den nedre gränsen för installationsmål.

  5. Återskapa och distribuera om.

Skapa ett VSIX-projekt

Om du vill skapa ett språktjänsttillägg med en LSP-baserad språkserver kontrollerar du först att du har Visual Studio-tilläggsutveckling arbetsbelastning installerad för din instans av VS.

Skapa sedan ett nytt VSIX-projekt genom att gå till File>New Project>Visual C#>Extensibility>VSIX Project:

skapa vsix-projekt

Språkserver och installation av körmiljö

Som standard innehåller tilläggen som skapats för att stödja LSP-baserade språkservrar i Visual Studio inte själva språkservrarna eller de körningar som krävs för att köra dem. Tilläggsutvecklare ansvarar för att distribuera språkservrarna och de nödvändiga körningarna. Det finns flera sätt att göra detta:

  • Språkservrar kan bäddas in i VSIX som innehållsfiler.
  • Skapa en MSI för att installera språkservern och/eller nödvändiga körtider.
  • Ge instruktioner på Marketplace för att informera användarna om hur de hämtar runtime-miljöer och språkservrar.

Grammatikfiler för TextMate

LSP innehåller inte specifikation för hur du tillhandahåller textfärgning för språk. För att tillhandahålla anpassad färgläggning för språk i Visual Studio kan tilläggsutvecklare använda en TextMate-grammatikfil. Följ dessa steg om du vill lägga till anpassad textmatik eller temafiler:

  1. Skapa en mapp med namnet "Grammatik" i tillägget (eller så kan det vara det namn du väljer).

  2. I mappen Grammars innehåller du alla *.tmlanguage, *.plist, *.tmthemeeller *.json filer som du vill ska ge anpassad färgsättning.

    Tips

    En .tmtheme--fil definierar hur omfången mappar till Visual Studio-klassificeringar (med namnet färgnycklar). Som vägledning kan du referera till den globala .tmtheme-filen i %ProgramFiles(x86)%\Microsoft Visual Studio\<version>\<SKU>\Common7\IDE\CommonExtensions\Microsoft\TextMate\Starterkit\Themesg directory.

  3. Skapa en .pkgdef- fil och lägg till en rad som liknar följande:

    [$RootKey$\TextMate\Repositories]
    "MyLang"="$PackageFolder$\Grammars"
    
  4. Högerklicka på filerna och välj Egenskaper. Ändra åtgärden Build till Content och ändra egenskapen Include in VSIX till true.

När du har slutfört föregående steg läggs mappen Grammatiker till i paketets installationskatalog som en lagringsplatskälla med namnet "MyLang" ("MyLang" är bara ett namn för tvetydighet och kan vara valfri unik sträng). Alla grammatiker (.tmlanguage- filer) och temafiler (.tmtheme-filer) i den här katalogen hämtas som potentialer och de ersätter de inbyggda grammatikerna som tillhandahålls med TextMate. Om grammatikfilens deklarerade tillägg matchar tillägget för filen som öppnas, kommer TextMate att ta över.

Skapa en enkel språkklient

Huvudgränssnitt – ILanguageClient

När du har skapat ditt VSIX-projekt lägger du till följande NuGet-paket i projektet:

Notera

När du är beroende av NuGet-paketet när du har slutfört föregående steg läggs även Newtonsoft.Json- och StreamJsonRpc-paketen till i projektet. Uppdatera inte dessa paket om du inte är säker på att de nya versionerna kommer att installeras på den version av Visual Studio som tillägget riktar sig mot. Sammansättningarna tas inte med i VSIX. I stället hämtas de från Visual Studio-installationskatalogen. Om du refererar till en nyare version av sammansättningarna än vad som är installerat på en användares dator fungerar inte tillägget.

Du kan sedan skapa en ny klass som implementerar gränssnittet ILanguageClient, vilket är det huvudsakliga gränssnitt som krävs för språkklienter som ansluter till en LSP-baserad språkserver.

Följande är ett exempel:

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;
        }
    }
}

De viktigaste metoderna som måste implementeras är OnLoadedAsync och ActivateAsync. OnLoadedAsync anropas när Visual Studio har läst in tillägget och språkservern är redo att startas. I den här metoden kan du anropa StartAsync- delegera omedelbart för att signalera att språkservern ska startas, eller så kan du göra ytterligare logik och anropa StartAsync senare. Om du vill aktivera språkservern måste du anropa StartAsync någon gång.

ActivateAsync är metoden som så småningom aktiveras genom att anropa delegeringen StartAsync. Den innehåller logiken för att starta språkservern och upprätta en anslutning till den. Ett anslutningsobjekt som innehåller strömmar för att skriva till servern och läsa från servern måste returneras. Undantag som uppstår här fångas upp och visas för användaren via ett InfoBar-meddelande i Visual Studio.

Aktivering

När din språkklientklass har implementerats måste du definiera två attribut för den för att definiera hur den ska läsas in i Visual Studio och aktiveras:

  [Export(typeof(ILanguageClient))]
  [ContentType("bar")]

MEF

Visual Studio använder MEF- (Managed Extensibility Framework) för att hantera sina utökningspunkter. Attributet Export anger för Visual Studio att den här klassen ska hämtas som en tilläggspunkt och läsas in vid lämplig tidpunkt.

Om du vill använda MEF måste du också definiera MEF som en tillgång i VSIX-manifestet.

Öppna VSIX-manifestdesignern och gå till fliken Tillgångar:

lägga till MEF-tillgång

Klicka på Ny för att skapa en ny tillgång:

definiera MEF-tillgång

  • Typ: Microsoft.VisualStudio.MefComponent
  • Source: Ett projekt i den nuvarande lösningen
  • Project: [Ditt projekt]

Definition av innehållstyp

För närvarande är det enda sättet att läsa in ditt LSP-baserade språkservertillägg beroende på filens innehållstyp. När du definierar din språkklientklass (som implementerar ILanguageClient) måste du definiera vilka typer av filer som, när det öppnas, kommer att göra att tillägget läses in. Om inga filer som matchar din definierade innehållstyp öppnas läses inte tillägget in.

Detta görs genom att definiera en eller flera ContentTypeDefinition klasser:

namespace MockLanguageExtension
{
    public class BarContentDefinition
    {
        [Export]
        [Name("bar")]
        [BaseDefinition(CodeRemoteContentDefinition.CodeRemoteContentTypeName)]
        internal static ContentTypeDefinition BarContentTypeDefinition;

        [Export]
        [FileExtension(".bar")]
        [ContentType("bar")]
        internal static FileExtensionToContentTypeDefinition BarFileExtensionDefinition;
    }
}

I föregående exempel skapas en definition av innehållstyp för filer som slutar i .bar filnamnstillägg. Innehållstypdefinitionen får namnet "bar" och måste härledas från CodeRemoteContentTypeName.

När du har lagt till en innehållstypsdefinition kan du definiera när du ska läsa in språkklienttillägget i språkklientklassen:

    [ContentType("bar")]
    [Export(typeof(ILanguageClient))]
    public class BarLanguageClient : ILanguageClient
    {
    }

Att lägga till stöd för LSP-språkservrar kräver inte att du implementerar ditt eget projektsystem i Visual Studio. Kunder kan öppna en enda fil eller en mapp i Visual Studio för att börja använda din språktjänst. I själva verket är stödet för LSP-språkservrar utformat för att endast fungera i öppna mapp-/filscenarier. Om ett anpassat projektsystem implementeras fungerar inte vissa funktioner (till exempel inställningar).

Avancerade funktioner

Inställningar

Stöd för anpassade språkserverspecifika inställningar är tillgängligt, men det håller fortfarande på att förbättras. Inställningarna är specifika för vad språkservern stöder och styr vanligtvis hur språkservern genererar data. En språkserver kan till exempel ha en inställning för det maximala antalet rapporterade fel. Tilläggsförfattare definierar ett standardvärde som kan ändras av användare för specifika projekt.

Följ de här stegen nedan om du vill lägga till stöd för inställningar i LSP-språktjänsttillägget:

  1. Lägg till en JSON-fil (till exempel MockLanguageExtensionSettings.json) i projektet som innehåller inställningarna och deras standardvärden. Till exempel:

    {
        "foo.maxNumberOfProblems": -1
    }
    
  2. Högerklicka på JSON-filen och välj Egenskaper. Ändra åtgärden Build till "Content" och egenskapen "Include in VSIX" till true.

  3. Implementera ConfigurationSections och returnera listan med prefix för inställningarna som definierats i JSON-filen (I Visual Studio Code mappas detta till konfigurationsavsnittets namn i package.json):

    public IEnumerable<string> ConfigurationSections
    {
        get
        {
            yield return "foo";
        }
    }
    
  4. Lägg till en .pkgdef-fil i projektet (lägg till en ny textfil och ändra filnamnstillägget till .pkgdef). Pkgdef-filen bör innehålla den här informationen:

    [$RootKey$\OpenFolder\Settings\VSWorkspaceSettings\[settings-name]]
    @="$PackageFolder$\[settings-file-name].json"
    

    Prov:

    [$RootKey$\OpenFolder\Settings\VSWorkspaceSettings\MockLanguageExtension]
    @="$PackageFolder$\MockLanguageExtensionSettings.json"
    
  5. Högerklicka på .pkgdef-filen och välj Egenskaper. Ändra åtgärden Build till Content och egenskapen Include in VSIX till true.

  6. Öppna filen source.extension.vsixmanifest och lägg till en tillgång på fliken Tillgång:

    redigera vspackage-

    • typ: Microsoft.VisualStudio.VsPackage
    • Source: Fil på filsystem
    • Path: [Sökväg till filen .pkgdef]

Användarredigering av inställningar för en arbetsyta

  1. Användaren öppnar en arbetsyta som innehåller filer som servern äger.

  2. Användaren lägger till en fil i mappen .vs med namnet VSWorkspaceSettings.json.

  3. Användaren lägger till en rad i VSWorkspaceSettings.json-filen för en inställning som servern tillhandahåller. Till exempel:

    {
        "foo.maxNumberOfProblems": 10
    }
    

Aktivera spårning av diagnostik

Diagnostikspårning kan aktiveras för att mata ut alla meddelanden mellan klienten och servern, vilket kan vara användbart vid felsökning av problem. Gör följande för att aktivera diagnostisk spårning:

  1. Öppna eller skapa filen för arbetsyteinställningar VSWorkspaceSettings.json (se "Användarredigering av inställningar för en arbetsyta").
  2. Lägg till följande rad i json-filen för inställningar:
{
    "foo.trace.server": "Off"
}

Det finns tre möjliga värden för spårningsveroalitet:

  • "Off": spårningen är helt avstängd
  • "Meddelanden": spårning aktiverat men endast metodnamn och svars-ID spåras.
  • "Utförlig": spårning aktiverad; hela RPC-meddelandet spåras.

När spårning är aktiverat skrivs innehållet till en fil i katalogen %temp%\VisualStudio\LSP. Loggen följer namngivningsformatet [LanguageClientName]-[Datetime Stamp].log. För närvarande kan spårning endast aktiveras för scenarier med öppna mappar. Om du öppnar en enda fil för att aktivera en språkserver har det inte stöd för diagnostikspårning.

Anpassade meddelanden

Det finns API:er på plats för att underlätta överföring av meddelanden till och ta emot meddelanden från språkservern som inte ingår i Standard Language Server Protocol. Om du vill hantera anpassade meddelanden implementerar du ILanguageClientCustomMessage2-gränssnittet i din språkklientklass. VS-StreamJsonRpc--biblioteket används för att överföra anpassade meddelanden mellan språkklienten och språkservern. Eftersom ditt LSP-språkklienttillägg är precis som andra Visual Studio-tillägg kan du välja att lägga till ytterligare funktioner (som inte stöds av LSP) i Visual Studio (med andra Visual Studio-API:er) i tillägget via anpassade meddelanden.

Ta emot anpassade meddelanden

Om du vill ta emot anpassade meddelanden från språkservern implementerar du egenskapen [CustomMessageTarget]((/dotnet/api/microsoft.visualstudio.languageserver.client.ilanguageclientcustommessage.custommessagetarget) på ILanguageClientCustomMessage2 och returnerar ett objekt som vet hur du hanterar dina anpassade meddelanden. Exempel nedan:

Egenskapen (/dotnet/api/microsoft.visualstudio.languageserver.client.ilanguageclientcustommessage.custommessagetarget) på ILanguageClientCustomMessage2 och returnerar ett objekt som vet hur du hanterar dina anpassade meddelanden. Exempel nedan:

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
        }
    }
}

Skicka anpassade meddelanden

Om du vill skicka anpassade meddelanden till språkservern implementerar du metoden AttachForCustomMessageAsyncILanguageClientCustomMessage2. Den här metoden anropas när språkservern startas och är redo att ta emot meddelanden. Ett JsonRpc--objekt skickas som en parameter, som du sedan kan behålla för att skicka meddelanden till språkservern med hjälp av VS-StreamJsonRpc API:er. Exempel nedan:

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);
    }
}

Mellanlager

Ibland kan en tilläggsutvecklare vilja fånga upp LSP-meddelanden som skickas till och tas emot från språkservern. En tilläggsutvecklare kanske till exempel vill ändra meddelandeparametern som skickas för ett visst LSP-meddelande eller ändra resultaten som returneras från språkservern för en LSP-funktion (till exempel slutföranden). När detta är nödvändigt kan tilläggsutvecklare använda MiddleLayer-API:et för att fånga upp LSP-meddelanden.

Om du vill fånga upp ett visst meddelande skapar du en klass som implementerar gränssnittet ILanguageClientMiddleLayer. Implementera sedan ILanguageClientCustomMessage2-gränssnittet i språkklientklassen och returnera en instans av objektet i egenskapen MiddleLayer. Exempel nedan:

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);
    }
  }
}

Funktionen för mellanskiktet är fortfarande under utveckling och är ännu inte omfattande.

Exempel på LSP-språkservertillägg

Information om hur du ser källkoden för ett exempeltillägg med hjälp av LSP-klient-API:et i Visual Studio finns i VSSDK-Extensibility-Samples LSP-exempel.

FAQ

jag vill skapa ett anpassat projektsystem för att komplettera min LSP-språkserver för att ge rikare funktionsstöd i Visual Studio, hur gör jag för att göra det?

Stöd för LSP-baserade språkservrar i Visual Studio förlitar sig på funktionen öppen mapp och är utformad för att inte kräva ett anpassat projektsystem. Du kan skapa ett eget anpassat projektsystem genom att följa anvisningarna här, men vissa funktioner, till exempel inställningar, kanske inte fungerar. Standardinitieringslogik för LSP-språkservrar är att skicka rotmappens plats för den mapp som för närvarande öppnas, så om du använder ett anpassat projektsystem kan du behöva ange anpassad logik under initieringen för att säkerställa att språkservern kan starta korrekt.

Hur lägger jag till felsökningsstöd?

Vi kommer att ge stöd för gemensamma felsökningsprotokollet i en framtida version.

Om det redan finns en VS-språktjänst installerad (till exempel JavaScript), kan jag fortfarande installera ett LSP-språkservertillägg som erbjuder ytterligare funktioner (till exempel linting)?

Ja, men alla funktioner fungerar inte korrekt. Det slutliga målet för LSP-språkservertillägg är att aktivera språktjänster som inte stöds internt av Visual Studio. Du kan skapa tillägg som ger ytterligare stöd med hjälp av LSP-språkservrar, men vissa funktioner (till exempel IntelliSense) är inte en smidig upplevelse. I allmänhet rekommenderas att LSP-språkservertillägg används för att tillhandahålla nya språkupplevelser, inte utöka befintliga.

Var publicerar jag min färdiga LSP-språkserver VSIX?

Se Marketplace-anvisningarna här.