Header menu logo F# Compiler Guide

F# LSP

F# LSP support design proposal. To be expanded as we learn more / settle on things.

Table of contents

Goals / Requirements

Overview


graph TD

VS(VS) -->|VS LSP client| NewVsix("NewVsix (Out-of-process)")

VsCode(VS Code) --> Ionide(Ionide/FSAC)

Vim(Vim) --> Ionide(Ionide/FSAC)

FsLSPServer(FsLSP Server)

NewVsix --> ProjectQuery(Project Query API)
NewVsix --> FsLSP

FsLSPServer --> FsLSP



subgraph FsLSP
ProjectModel(Project Model)
WorkspaceStateManagement(Workspace State Management)
LSPLibrary(LSP Library)
end

FsLSP --> FCS

Ionide --> FsLSPServer
Ionide --> ProjInfo(ProjInfo)

style VS stroke-dasharray: 2 4
style Vim stroke-dasharray: 2 4
style VsCode stroke-dasharray: 2 4
style Ionide stroke-dasharray: 2 4
style ProjInfo stroke-dasharray: 2 4
style LSPLibrary stroke-dasharray: 2 4
style ProjectQuery stroke-dasharray: 2 4

Component

Description

Release Process

FCS

F# Compiler Service

Nuget

FsLSP

The F# LSP library. This can be used to create an LSP server.

Nuget

  • FsLSPServer

The F# LSP server. An executable that can be started and will process LSP calls. Thin wrapper around FsLSP.

with FsLSP

  • Workspace State Management

A system for keeping a "current snapshot" of projects the user is working with

  • Project Model

A model for a project system we will use since we can't be tied to any particular one

  • LSP Library

A ready made library we will hopefully find to simplify LSP server development

NewVsix

The new VS Extension which will be powered by LSP

with VS

Components

FsLSP

The library containing the LSP functionality. Can be used to create an LSP server by adding the transport layer.

FsLSPServer

The LSP server executable using STD IN/OUT for transport.

Will be packaged with FsLSP and available as a dotnet tool (?)

It should always target the latest .NET runtime.

Workspace State Management

This part of the library is responsible for keeping track of the current workspace state. The internal workspace model is used to create requests for FCS. It needs to make sure the internal state doesn't get out of sync with what's in the editor and on disk.

Things to keep track of:

Project model

We need some internal model for a workspace/solution with projects. We don't want to use any project system library because we don't need most of the functionality which will be instead handled by the client.

The model needs to be able to quickly absorb incoming changes and easily convert to (or already contain) inputs for FCS. We could consider a reactive/adaptive approach for this.

We should keep track of versions of each project and source file including all dependencies with regards to parsing and type checking.

Example sequence diagram


sequenceDiagram

participant Client
participant FsLSP
participant WorkspaceStateManagement
participant FCS

Client->>FsLSP: textDocument/didChange
FsLSP->>WorkspaceStateManagement: Update workspace state
Client->>+FsLSP: textDocument/diagnostic
FsLSP->>+WorkspaceStateManagement: Get current workspace snapshot
WorkspaceStateManagement-->>-FsLSP: Current workspace snapshot
FsLSP->>+FCS: ParseAndCheckFileInProject
FCS-->>-FsLSP: FSharpCheckFileResults
FsLSP-->>-Client: DocumentDiagnosticReport

Concurrency and cancellation

We need to make sure when a project snapshot is needed to process a request, it will always contain all change notifications that were received before. There shouldn't be need for a global request queue, possibly only for change notifications inside WorkspaceStateManagement. And any language requests will wait until WorkspaceStateManagement has absorbed all previous changes.

For cancellation we should decide, potentially on a case-by-case basis what to do when an incoming change notification invalidates an ongoing request.

  1. Do nothing and let the request run either until it completes or is cancelled by the client. This is the easiest so we will probably start with this.
  2. Cancel the request and return "cancelled" result to the client. The client then needs to deal with it.
  3. Internally restart the work updated inputs and complete the original request until cancelled. This mode might save a bunch of back-and-forth with the client when user is typing. But it requires the client to expect it and not send cancellations a re-requests for the same thing.
Request update example

sequenceDiagram

participant Client
participant FsLSP
participant WorkspaceStateManagement
participant FCS

Client->>+FsLSP: textDocument/diagnostic
FsLSP->>+WorkspaceStateManagement: Get current workspace snapshot
WorkspaceStateManagement-->>-FsLSP: Current workspace snapshot
FsLSP->>FCS: ParseAndCheckFileInProject
activate FCS
Client->>FsLSP: textDocument/didChange
FsLSP->>FCS: Cancel ParseAndCheckFileInProject
deactivate FCS
FsLSP->>WorkspaceStateManagement: Update workspace state
FsLSP->>+WorkspaceStateManagement: Get current workspace snapshot
WorkspaceStateManagement-->>-FsLSP: Current workspace snapshot

FsLSP->>+FCS: ParseAndCheckFileInProject
FCS-->>-FsLSP: FSharpCheckFileResults
FsLSP-->>-Client: DocumentDiagnosticReport

Request tracking

This will hopefully be handled by the LSP library. Although we might need to be able to keep track of requests in progress for the purpose of cancellation.

Configuration updates

We should be able to apply configuration changes that require re-creating the FCS checker. We should be able to keep the caches since the results shouldn't depend on checker settings (rather only the input Project Snapshots).

Extensibility

The FsLSP library should be extensible by adding custom endpoints or overriding existing ones.

It should also allow to hook into background processes or events to customize behavior.

The code for extending the behavior should look the same as the code for implementing the core functionality. That way it's easy to test out and then potentially contribute it directly to the library.

Release process

We should use a similar release process as we have now for FCS. Potentially also provide a pre-release package with the latest version from main.

NewVsix

New VS extension that will be backed by the LSP server. It should mostly contain plumbing to connect LSP to the editor and CPS.

We should build on new VisualStudio.Extensibility model to run the extension out-of-process. It can also directly connect native VS LSP directly to our server which we can host directly in the extension.

We might need a wrapper C# project because the new extensibility model relies on source generation.

We should be able to load project information via ProjectQuery API to which we can also subscribe for changes.

The new extension should be able to run side by side with the current and extension gradually take over the functionality we will add to LSP.

If there is any missing functionality in any of these, we might need to supplement it from our current MEF extension and connect from it to the new one.

FCS

The only strict requirement on FCS is to be able to process open files from memory and non-open files from filesystem. But probably best to abstract that within FsLSP and just provide ISourceText objects for both cases.

At the moment this is possible via the experimental DocumentSource.Custom parameter. But hopefully we will be able to use a new FSharpProjectSnapshot model (from #15179) which combines all the necessary inputs into a single object.

LSP library

The library needs to support all the features we want to implement including the ability to add custom F# ones. It also shouldn't incur an unnecessary performance overhead.

Candidates

Library

Pros

Cons

We write our own

  • Complete control over everything
  • A lot of extra work

Ionide LanguageServerProtocol

  • Battle tested with Ionide and FSAC + Natural F# API
  • Might not be able to fully customize it to our need

Roslyn CLaSP

  • Should be performant + Future support
  • Not stable API - Unnatural C# API

OmniSharp C# LSP

  • Unnatural C# API

It probably makes sense to start with an existing library which should be able to get us to a working product and only then consider writing our own in case it's the only way to enable more functionality or performance.

LSP client (?)

Diagnostic mode

Initially we will be offering the Pull Diagnostics mode where the client will request diagnostics for a particular document. It will be up to the client to ask for diagnostics for documents at appropriate times and cancel requests that are no longer needed. The client can also use this to implement background checking of all documents in the workspace.

Later we can also add an option to have the server control diagnostics and publish them via Publish Diagnostics. Then it will be pushing diagnostics as they are discovered either for active document, open documents or all documents.

LSIF

We should eventually add support for LSIF. At the moment it looks like we would have to construct it manually (as in, no helpful libraries are available).

This is probably lower priority than anything else.

It might also be a good opportunity to re-think how we store symbols for purposes of finding references, go to definition, or code search (go to symbol in workspace by name/query).

Performance

Areas to pay attention to with regards to performance

Analyzers & Refactoring support

These will hopefully not require any special treatment and can just use existing LSP APIs.

Mixed F# / C# solutions

We need to figure out how to talk to C# LSP server for:

Multiple workspace support

We should be able to use a single instance of LSP server even for multiple workspaces. We can keep a separate instance of WorkspaceStateManagement for each workspace but send language requests to a single instance of FCS checker. This way it's possible to reuse some results if the workspaces share the same projects (with the same options).

Action plan

A rough draft of how to plan the work and which items can be done in parallel.

Phase 0

We need to investigate which system we'll use for the New Vsix. Ideally we're able to go with new VisualStudio.Extensibility but if that has any blockers we might need to stick to in-process MEF.

We can prototype the New Vsix with some sample LSP server or maybe with an existing one like FSAC. Just to figure out how to connect it to F# projects in VS.

For FsLSP we need to choose a library and implement a dummy LSP server with it that we can connect to the New Vsix and get some sort of basic setup that can then be improved upon.


gantt
    axisFormat .
    section FsLSP
        Choose LSP library :fs1, 1900-01-01, 1d
        Add projects for FsLSP and FsLSPServer :fs2, after fs1, 1d
        Implement dummy LSP server using the library :fs3, after fs2, 1d
        Connect to NewVsix :after vsix2 fs3, 1d
    section Vsix
        Investigate options for NewVsix :vsix1, 1900-01-01, 1d
        Implement NewVsix prototype with dummy or some existing LSP server :vsix2, after vsix1, 2d

Phase 1

Get some sort of working prototype that can do semantic highlighting and show diagnostics.

Figure out how to run side-by-side and gradually take over functionality from the current Vsix, if possible.


gantt
    axisFormat .
    section FsLSP
        Implement WorkspaceStateManagement :fs1, 1900-01-01, 3d
        Implement Phase 1 endpoints :fs2, after fs1, 2d
    section Vsix
        Investigate running side-by-side with old Vsix :vsix1, 1900-01-01, 1d
        Implement configuration of which features are handled by which Vsix :vsix2, after vsix1, 2d

Phase 2 - 5

Implement endpoints based on their assigned priority. See next section.

Independently we can start on setting up the build & release process for FsLSP and insertions of NewVsix into VS.

Another potentially parallel track can be figuring out interop with C# LSP server for mixed solutions.


gantt
    axisFormat .
    section FsLSP
        Implement Phase 2 endpoints :fs1, 1900-01-01, 1d
        Implement Phase 3 endpoints :fs2, after fs1, 1d
        Implement Phase 4 endpoints :fs3, after fs2, 1d
        Implement Phase 5 endpoints :fs4, after fs3, 1d
    section Distribution
        Settle on release process :d1, 1900-01-01, 1d
        Add / update build pipelines :d2, after d1, 1d
        Set up VS insertions :d3, after d2, 1d

    section Cs interop
        Figure out how to talk to CSharp LSP server :c1, 1900-01-01, 4d

LSP Endpoints

This is a preliminary list of LSP APIs we want (or don't want) to implement, roughly ordered by priority. We will probably re-classify some of them as we go.

Name

Type

Direction

Category

Priority / Dev phase

Supported by FSAC

Note

initialize

Request

↩️

Lifecycle

0

initialized

Notification

➡️

Lifecycle

0

textDocument/didChange

Notification

➡️

Document synchronization

1

textDocument/didClose

Notification

➡️

Document synchronization

1

textDocument/didOpen

Notification

➡️

Document synchronization

1

textDocument/diagnostic

Request

↩️

Language features

1

textDocument/semanticTokens/full

Request

↩️

Language features

1

$/logTrace

Notification

⬅️

Lifecycle

1

$/setTrace

Notification

➡️

Lifecycle

1

exit

Notification

➡️

Lifecycle

1

shutdown

Request

↩️

Lifecycle

1

completionItem/resolve

Request

↩️

Language features

2

textDocument/completion

Request

↩️

Language features

2

textDocument/declaration

Request

↩️

Language features

2

textDocument/definition

Request

↩️

Language features

2

textDocument/hover

Request

↩️

Language features

2

textDocument/implementation

Request

↩️

Language features

2

textDocument/references

Request

↩️

Language features

2

textDocument/semanticTokens/full/delta

Request

↩️

Language features

2

textDocument/semanticTokens/range

Request

↩️

Language features

2

textDocument/typeDefinition

Request

↩️

Language features

2

workspace/semanticTokens/refresh

Request

↩️

Language features

2

fsharp/compilerLocation

Request

↩️

F# Custom

3

fsharp/documentAnalyzed

Notification

⬅️

F# Custom

3

fsharp/documentation

Request

↩️

F# Custom

3

fsharp/documentationGenerator

Request

↩️

F# Custom

3

fsharp/documentationSymbol

Request

↩️

F# Custom

3

fsharp/dotnetaddproject

Request

↩️

F# Custom

3

fsharp/dotnetaddsln

Request

↩️

F# Custom

3

fsharp/dotnetnewlist

Request

↩️

F# Custom

3

fsharp/dotnetnewrun

Request

↩️

F# Custom

3

fsharp/dotnetremoveproject

Request

↩️

F# Custom

3

fsharp/fileParsed

Notification

⬅️

F# Custom

3

fsharp/lineLens

Request

↩️

F# Custom

3

fsharp/notifyCancel

Notification

⬅️

F# Custom

3

fsharp/notifyWorkspace

Notification

⬅️

F# Custom

3

fsharp/notifyWorkspacePeek

Notification

⬅️

F# Custom

3

fsharp/pipelineHint

Request

↩️

F# Custom

3

fsharp/project

Request

↩️

F# Custom

3

fsharp/signature

Request

↩️

F# Custom

3

fsharp/signatureData

Request

↩️

F# Custom

3

fsharp/testDetected

Notification

⬅️

F# Custom

3

fsharp/workspaceLoad

Request

↩️

F# Custom

3

fsharp/workspacePeek

Request

↩️

F# Custom

3

fsproj/addExistingFile

Request

↩️

F# Custom

3

fsproj/addFile

Request

↩️

F# Custom

3

fsproj/addFileAbove

Request

↩️

F# Custom

3

fsproj/addFileBelow

Request

↩️

F# Custom

3

fsproj/moveFileDown

Request

↩️

F# Custom

3

fsproj/moveFileUp

Request

↩️

F# Custom

3

fsproj/removeFile

Request

↩️

F# Custom

3

textDocument/documentHighlight

Request

↩️

Language features

3

textDocument/documentSymbol

Request

↩️

Language features

3

textDocument/formatting

Request

↩️

Language features

3

textDocument/rename

Request

↩️

Language features

3

textDocument/signatureHelp

Request

↩️

Language features

3

$/progress

Notification

⬅️

Window Features

3

Would be nice to have for long running operations

workspace/didChangeWatchedFiles

Notification

➡️

Workspace Features

3

textDocument/willSave

Notification

➡️

Document synchronization

4

textDocument/willSaveWaitUntil

Request

↩️

Document synchronization

4

codeAction/resolve

Request

↩️

Language features

4

codeLens/resolve

Request

↩️

Language features

4

inlayHint/resolve

Request

↩️

Language features

4

textDocument/codeAction

Request

↩️

Language features

4

textDocument/codeLens

Request

↩️

Language features

4

textDocument/prepareRename

Request

↩️

Language features

4

textDocument/publishDiagnostics

Notification

⬅️

Language features

4

workspace/codeLens/refresh

Request

↪️

Language Features

4

window/showMessage

Notification

⬅️

Window Features

4

workspace/configuration

Request

↪️

Workspace Features

4

workspace/diagnostic

Request

↩️

Workspace Features

4

workspace/didChangeConfiguration

Notification

➡️

Workspace Features

4

workspace/symbol

Request

↩️

Workspace Features

4

workspaceSymbol/resolve

Request

↩️

Workspace Features

4

notebookDocument/didChange

Notification

➡️

Notebook Document synchronization

5

notebookDocument/didClose

Notification

➡️

Notebook Document synchronization

5

notebookDocument/didOpen

Notification

➡️

Notebook Document synchronization

5

notebookDocument/didSave

Notification

➡️

Notebook Document synchronization

5

fsharp/f1Help

Request

↩️

F# Custom

fsharp/fsdn

Request

↩️

F# Custom

fsharp/loadAnalyzers

Request

↩️

F# Custom

callHierarchy/incomingCalls

Request

↩️

Language features

callHierarchy/outgoingCalls

Request

↩️

Language features

documentLink/resolve

Request

↩️

Language features

textDocument/documentLink

Request

↩️

Language features

Is this useful for us?

textDocument/inlayHint

Request

↩️

Language features

textDocument/inlineValue

Request

↩️

Language features

textDocument/moniker

Request

↩️

Language features

textDocument/prepareCallHierarchy

Request

↩️

Language features

textDocument/prepareTypeHierarchy

Request

↩️

Language features

textDocument/rangeFormatting

Request

↩️

Language features

textDocument/selectionRange

Request

↩️

Language features

typeHierarchy/subtypes

Request

↩️

Language features

typeHierarchy/supertypes

Request

↩️

Language features

client/registerCapability

Request

↩️

Lifecycle

client/unregisterCapability

Request

↩️

Lifecycle

telemetry/event

Notification

⬅️

Window Features

window/logMessage

Notification

⬅️

Window Features

window/workDoneProgress/cancel

Notification

⬅️

Window Features

window/workDoneProgress/create

Request

↩️

Window Features

workDoneProgress/cancel

Notification

➡️

Window Features

workDoneProgress/create

Request

⬅️

Window Features

workspace/applyEdit

Request

↪️

Workspace Features

Could be used for refactoring?

workspace/diagnostic/refresh

Request

↪️

Workspace Features

workspace/executeCommand

Request

↩️

Workspace Features

Could be used for refactoring?

textDocument/didSave

Notification

➡️

Document synchronization

Probably don't need this?

textDocument/colorPresentation

Request

↩️

Language features

textDocument/documentColor

Request

↩️

Language features

textDocument/linkedEditingRange

Request

↩️

Language features

textDocument/onTypeFormatting

Request

↩️

Language features

window/showDocument

Request

↪️

Window Features

window/showMessageRequest

Request

↪️

Window Features

workspace/didChangeWorkspaceFolders

Notification

➡️

Workspace Features

workspace/didCreateFiles

Notification

➡️

Workspace Features

workspace/didDeleteFiles

Notification

➡️

Workspace Features

workspace/didRenameFiles

Notification

➡️

Workspace Features

workspace/willCreateFiles

Notification

➡️

Workspace Features

workspace/willDeleteFiles

Notification

➡️

Workspace Features

workspace/willRenameFiles

Notification

➡️

Workspace Features

workspace/workspaceFolders

Request

⬅️

Workspace Features

Type something to start searching.