Back to Roslyn

Common Language Server Protocol Framework (CLaSP)

src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/README.md

11.0.1009.1 KB
Original Source

Common Language Server Protocol Framework (CLaSP)

CLaSP was created to ease the creation and maintenance of Language Server implementations by sharing our hard-won knowledge, while leaving you the flexiblity to create the server you need.

A note on support

Currently CLaSP is not recommended or supported for use outside of specific teams/projects. We hope to make it more broadly available in the future.

Getting Started with CLaSP

You can find a simple example implementation of a CLaSP-based LanguageServer here.

To get started with CLaSP you may follow the following steps

  1. Create a PackageReference to Microsoft.CommonLanguageServerProtocol.Framework in your LSP server project.
  2. Implement the following classes:
    1. ILspLogger. This allows CLaSP to log information however you would like.
    2. ILspServices. This interface will serve as a wrapper around whatever DI system you choose to use to make sure that your services implemented elsewhere are available to CLaSP.
      1. We recommend making SupportsGetRegisteredServices return false and GetRegisteredServices throw NotImplementedException.
    3. AbstractLanguageServer. This is the core of your Language Server implementation, which will manage your lifecycle and host the rest of the components.
      1. Ensure that Initialize is called (preferably from the constructor) to start the server.
      2. ConstructLspServices will be called once to construct your implementation of ILspServices. When this exits all your services should be registerd, including your IMethodHandlers. The following Services are mandatory for proper function:
        • An ILspLogger.
        • An IRequestContextFactory.
        • An InitializeHandler and InitializedHandler, either the ones included in CLaSP or your own implementations. If you use the included handlers you will need an IInitializeManager (which handles your Client and Server Capabilities) too.
        • The AbstractLanguageServer itself.
    4. IRequestContextFactory<TRequestContext>. Constructs the RequestContext for each request. To maintain good performance you need to minimize the work being done in CreateRequestContextAsync since it blocks the queue from receiving mutating requests.
      1. A RequestContext type. This is an object which will be passed in on every request your Handlers handle. It is a useful place to keep things like Loggers, Service providers, and most importantly DocumentSnapshots.
  3. Now implement any Method handlers you need for your Language Server to properly function, such as textDocument/didOpen using the INotificationHandler and IRequestHandler interfaces (and the ITextDocumeentIdentifierhandler<TRequest,TTextDocumentIdentifier> interface if the request relates to a specific document), being sure to include them in your ILspServices object (as constructed by ConstructLspServices) as IMethodHandler services. This automatically registers them on the JsonRpc object.
    1. MutatesSolutionState should be true for Handlers like textDocument/didChange which change solution state because this affects the queuing behavior required in order to ensure that the correct document version is being operated on.
    2. HandleNotificationAsync and HandleRequestAsync. These implement your actual handler behavior but it's very important that if they require access to the current state of a TextDocument that this information be gathered by IRequestContextFactory and put on the RequestContext object rather than retrieved here. If you fail to follow this stipulation you may run into document sync issues because requests which mutate document state are not guaranteed to happen in a particular order. This means that document state might change while your HandleRequestAsync request is executing, but the IRequestContextFactory is guaranteed to operate in a thread-safe manner.

More complex examples

  • Roslyn is the original implementor of CLaSP.
    • AbstractLanguageServer. Note that Roslyn overrides ConstructRequestExecutionQueue, allowing it to change the override some default behavior.
    • ILSPLogger. Logs to LogHub.
    • ILSPServices. Note: Roslyns ILSPServices implementation is a product of their specific history and needs and ends up being a combination of MEF and explicitly constructed services. We don't recommend using it to guide your creation of an ILSPServices implementation unless you have similarly complicated needs.
    • IRequestContextFactory. Provides a good example of how to get the TextDocumentIdentifier (URI) off of the requestParam object.
    • IRequestHandler or INotificationHandler. Note that this example mutates solution state and has document context.
  • Razor has simpler requirements than Roslyn in some ways (such as ILSPServices) but relies on multiple other language servers (C#, HTML) for information on its contained languages.