Delen via


Taalserverprotocol

Wat is het Language Server Protocol?

Het ondersteunen van uitgebreide bewerkingsfuncties zoals automatische voltooiing van broncode of Go to Definition voor een programmeertaal in een editor of IDE is traditioneel erg lastig en tijdrovend. Meestal moet een domeinmodel (een scanner, een parser, een typecontrole, een opbouwfunctie en meer) worden geschreven in de programmeertaal van de editor of IDE. De Eclipse CDT-invoegtoepassing, die bijvoorbeeld ondersteuning biedt voor C/C++ in de Eclipse IDE, wordt geschreven in Java omdat de Eclipse IDE zelf is geschreven in Java. Als u deze aanpak volgt, betekent dit dat u een C/C++-domeinmodel implementeert in TypeScript voor Visual Studio Code en een afzonderlijk domeinmodel in C# voor Visual Studio.

Het maken van taalspecifieke domeinmodellen is ook veel eenvoudiger als een ontwikkelprogramma bestaande taalspecifieke bibliotheken kan hergebruiken. Deze bibliotheken worden echter meestal geïmplementeerd in de programmeertaal zelf (bijvoorbeeld goede C/C++-domeinmodellen worden geïmplementeerd in C/C++). Het integreren van een C/C++-bibliotheek in een editor die in TypeScript is geschreven, is technisch mogelijk, maar moeilijk te doen.

Taalservers

Een andere benadering is door de bibliotheek in een eigen proces te laten draaien en interprocesscommunicatie te gebruiken om ermee te communiceren. De berichten die heen en weer worden verzonden, vormen een protocol. Het LSP (Language Server Protocol) is het product van het standaardiseren van de berichten die worden uitgewisseld tussen een ontwikkelprogramma en een taalserverproces. Het gebruik van taalservers of demonen is geen nieuw of nieuw idee. Editors zoals Vim en Emacs doen dit al enige tijd om semantische ondersteuning voor automatische voltooiing te bieden. Het doel van de LSP was om dit soort integraties te vereenvoudigen en een nuttig kader te bieden voor het blootstellen van taalfuncties aan verschillende hulpprogramma's.

Door een gemeenschappelijk protocol te hebben, kan de integratie van programmeertaalfuncties met minimale problemen worden geïntegreerd in een ontwikkelhulpprogramma door een bestaande implementatie van het domeinmodel van de taal opnieuw te gebruiken. Een back-end van een taalserver kan worden geschreven in PHP, Python of Java en de LSP kan eenvoudig worden geïntegreerd in verschillende hulpprogramma's. Het protocol werkt op een gemeenschappelijk abstractieniveau, zodat een hulpprogramma uitgebreide taalservices kan bieden zonder de nuances die specifiek zijn voor het onderliggende domeinmodel volledig te begrijpen.

Hoe werken aan de LSP is gestart

De LSP is in de loop van de tijd ontwikkeld en vandaag is het op versie 3.0. Het begon toen het concept van een taalserver werd opgehaald door OmniSharp om uitgebreide bewerkingsfuncties voor C# te bieden. In eerste instantie gebruikte OmniSharp het HTTP-protocol met een JSON-nettolading en is geïntegreerd in verschillende editors, waaronder Visual Studio Code.

Rond dezelfde tijd begon Microsoft aan een TypeScript-taalserver te werken, met het idee om TypeScript te ondersteunen in editors zoals Emacs en Sublieme tekst. In deze implementatie communiceert een editor via stdin/stdout met het TypeScript-serverproces en maakt gebruik van een JSON-nettolading die is geïnspireerd op het V8-foutopsporingsprogrammaprotocol voor aanvragen en antwoorden. De TypeScript-server is geïntegreerd in de invoegtoepassing TypeScript Sublieme en VS Code voor uitgebreide TypeScript-bewerking.

Nadat het VS Code-team twee verschillende taalservers heeft geïntegreerd, is begonnen met het verkennen van een gemeenschappelijk taalserverprotocol voor editors en IDE's. Met een algemeen protocol kan een taalprovider één taalserver maken die kan worden gebruikt door verschillende IDE's. Een taalservergebruiker hoeft slechts één keer de clientzijde van het protocol te implementeren. Dit resulteert in een win-win situatie voor zowel de taalprovider als de taalgebruiker.

Het taalserverprotocol is gestart met het protocol dat wordt gebruikt door de TypeScript-server, waarmee het wordt uitgebreid met meer taalfuncties die geïnspireerd zijn op de TAAL-API van VS Code. Het protocol wordt ondersteund met JSON-RPC voor externe aanroep vanwege de eenvoud en bestaande bibliotheken.

Het VS Code-team heeft het protocol geprototyped door verschillende linter-taalservers te implementeren die reageren op verzoeken om een bestand te linten en een set van gedetecteerde waarschuwingen en fouten terug te geven. Het doel was om een bestand te linten terwijl de gebruiker in een document bewerkt, wat betekent dat er tijdens een editorsessie veel linting-aanvragen zijn. Het is logisch om een server actief te houden, zodat een nieuw lintingproces niet hoeft te worden gestart voor elke gebruikersbewerking. Verschillende linter-servers zijn geïmplementeerd, waaronder de ESLint- en TSLint-extensies van VS Code. Deze twee linterservers worden beide geïmplementeerd in TypeScript/JavaScript en worden uitgevoerd op Node.js. Ze delen een bibliotheek die het client- en servergedeelte van het protocol implementeert.

Hoe de LSP werkt

Een taalserver wordt uitgevoerd in een eigen proces en hulpprogramma's zoals Visual Studio of VS Code communiceren met de server met behulp van het taalprotocol via JSON-RPC. Een ander voordeel van de taalserver die in een speciaal proces werkt, is dat prestatieproblemen met betrekking tot één procesmodel worden vermeden. Het daadwerkelijke transportkanaal kan ofwel stdio, sockets, named pipes of node ipc zijn als zowel de client als de server zijn geschreven in Node.js.

Hieronder ziet u een voorbeeld van hoe een hulpprogramma en een taalserver communiceren tijdens een routinebewerkingssessie:

LSP-stroomdiagram

  • De gebruiker opent een bestand (aangeduid als een document) in het hulpprogramma: het hulpprogramma meldt de taalserver dat een document is geopend ('textDocument/didOpen'). Vanaf nu bevindt de waarheid over de inhoud van het document zich niet meer in het bestandssysteem, maar bewaard door het hulpprogramma in het geheugen.

  • De gebruiker voert bewerkingen uit: het hulpprogramma geeft de server een bericht over de wijziging van het document ('textDocument/didChange') en de semantische informatie van het programma wordt bijgewerkt door de taalserver. Als dit gebeurt, analyseert de taalserver deze informatie en waarschuwt het hulpprogramma met de gedetecteerde fouten en waarschuwingen ('textDocument/publishDiagnostics').

  • De gebruiker voert 'Ga naar definitie' uit op een symbool in de editor: het hulpprogramma verzendt een aanvraag 'textDocument/definition' met twee parameters: (1) de document-URI en (2) de tekstpositie van waaruit de aanvraag Ga naar definitie is geïnitieerd naar de server. De server reageert met de document-URI en de positie van de definitie van het symbool in het document.

  • De gebruiker sluit het document (bestand): er wordt een melding 'textDocument/didClose' verzonden vanuit het hulpprogramma, waarbij de taalserver wordt geïnformeerd dat het document nu niet meer in het geheugen staat en dat de huidige inhoud nu up-to-date is op het bestandssysteem.

In dit voorbeeld ziet u hoe het protocol communiceert met de taalserver op het niveau van editorfuncties zoals 'Ga naar definitie', 'Alle verwijzingen zoeken'. De gegevenstypen die door het protocol worden gebruikt, zijn editor- of IDE-gegevenstypen, zoals het momenteel geopende tekstdocument en de positie van de cursor. De gegevenstypen bevinden zich niet op het niveau van een domeinmodel voor programmeertalen dat meestal abstracte syntaxisstructuren en compilersymbolen zou bieden (bijvoorbeeld opgeloste typen, naamruimten, ...). Dit vereenvoudigt het protocol aanzienlijk.

Laten we nu de aanvraag 'textDocument/definition' nader bekijken. Hieronder vindt u de payloads die tussen het clienthulpprogramma en de taalserver worden uitgewisseld voor de Ga-naar-definitie-aanvraag in een C++-document.

Dit is de aanvraag:

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

Dit is het antwoord:

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

Achteraf is het beschrijven van de gegevenstypen op het niveau van de editor in plaats van op het niveau van het programmeertaalmodel een van de redenen voor het succes van het taalserverprotocol. Het is veel eenvoudiger om een tekstdocument-URI of cursorpositie te standaardiseren vergeleken met het standaardiseren van een abstracte syntaxisstructuur en compilersymbolen in verschillende programmeertalen.

Wanneer een gebruiker met verschillende talen werkt, start VS Code doorgaans een taalserver voor elke programmeertaal. In het onderstaande voorbeeld ziet u een sessie waarin de gebruiker werkt op Java- en SASS-bestanden.

Java en Sass

Capabilities

Niet elke taalserver kan alle functies ondersteunen die zijn gedefinieerd door het protocol. Daarom kondigt de client en server hun ondersteunde functieset aan via 'capabilities'. Een server kondigt bijvoorbeeld aan dat deze de aanvraag 'textDocument/definition' kan verwerken, maar de aanvraag 'werkruimte/symbool' wordt mogelijk niet verwerkt. Op dezelfde manier kunnen clients aankondigen dat ze 'voornemens op te slaan'-meldingen kunnen geven voordat een document wordt opgeslagen, zodat een server tekstuele bewerkingen kan berekenen om het bewerkte document automatisch op te maken.

Een taalserver integreren

De daadwerkelijke integratie van een taalserver in een bepaald hulpprogramma wordt niet gedefinieerd door het taalserverprotocol en wordt overgelaten aan de hulpprogramma-implementors. Sommige hulpprogramma's integreren taalservers algemeen door een extensie te hebben die kan worden gestart en met elk type taalserver kan communiceren. Andere, zoals VS Code, maken een aangepaste extensie per taalserver, zodat een extensie nog steeds bepaalde aangepaste taalfuncties kan bieden.

Om de implementatie van taalservers en -clients te vereenvoudigen, zijn er bibliotheken of SDK's voor de client- en serveronderdelen. Deze bibliotheken zijn beschikbaar voor verschillende talen. Er is bijvoorbeeld een npm-module voor taalclients om de integratie van een taalserver in een VS Code-extensie en een andere npm-module voor taalservers te vereenvoudigen om een taalserver te schrijven met behulp van Node.js. Dit is de huidige lijst met ondersteuningsbibliotheken.

Het Language Server Protocol gebruiken in Visual Studio