[ WEBSITE | ISSUES | FORUM | CHANGELOG ]
This package implements a language server protocol (LSP) client for the CodeMirror code editor.
The project page has more information, a number of examples and the documentation.
Note that this code does not have a license yet. That should soon change.
We aim to be an inclusive, welcoming community. To make that explicit, we have a code of conduct that applies to communication around the project.
There are various ways to run a language server and connect it to a
web page. You can run it on the server and proxy it through a web
socket, or, if it is written in JavaScript or can be compiled to WASM,
run it directly in the client. The @codemirror/lsp-client package
talks to the server through a (Transport
)
object, which exposes a small interface for sending and receiving JSON
messages.
Responsibility for how to actually talk to the server, how to connect and to handle disconnects are left to the code that implements the transport.
This example uses a crude transport that doesn't handle errors at all.
import {Transport, LSPClient, languageServerSupport} from "@codemirror/lsp-client"
import {basicSetup, EditorView} from "codemirror"
import {typescriptLanguage} from "@codemirror/lang-javascript"
function simpleWebSocketTransport(uri: string): Promise<Transport> {
let handlers: ((value: string) => void)[] = []
let sock = new WebSocket(uri)
sock.onmessage = e => { for (let h of handlers) h(e.data.toString()) }
return new Promise(resolve => {
sock.onopen = () => resolve({
send(message: string) { sock.send(message) },
subscribe(handler: (value: string) => void) { handlers.push(handler) },
unsubscribe(handler: (value: string) => void) { handlers = handlers.filter(h => h != handler) }
})
})
}
let transport = await simpleWebSocketTransport("ws://host:port")
let client = new LSPClient().connect(transport)
new EditorView({
extensions: [
basicSetup,
typescriptLanguage,
languageServerSupport(client, "file:///some/file.ts"),
],
parent: document.body
})
-
class
LSPClient An LSP client manages a connection to a language server. It should be explicitly connected before use.
-
new LSPClient(config?: LSPClientConfig = {})
Create a client object.
-
workspace: Workspace
The client's workspace.
-
serverCapabilities: ServerCapabilities | null
The capabilities advertised by the server. Will be null when not connected or initialized.
-
initializing: Promise<null>
A promise that resolves once the client connection is initialized. Will be replaced by a new promise object when you call
disconnect
.-
connected: boolean
Whether this client is connected (has a transport).
-
connect(transport: Transport) → LSPClient
Connect this client to a server over the given transport. Will immediately start the initialization exchange with the server, and resolve
this.initializing
(which it also returns) when successful.-
disconnect()
Disconnect the client from the server.
-
didOpen(file: WorkspaceFile)
Send a
textDocument/didOpen
notification to the server.-
didClose(uri: string)
Send a
textDocument/didClose
notification to the server.-
request<Params, Result>(method: string, params: Params) → Promise<Result>
Make a request to the server. Returns a promise that resolves to the response or rejects with a failure message. You'll probably want to use types from the
vscode-languageserver-protocol
package for the type parameters.The caller is responsible for synchronizing state before the request and correctly handling state drift caused by local changes that happend during the request.
-
notification<Params>(method: string, params: Params)
Send a notification to the server.
-
cancelRequest(params: any)
Cancel the in-progress request with the given parameter value (which is compared by identity).
-
workspaceMapping() → WorkspaceMapping
Create a workspace mapping that tracks changes to files in this client's workspace, relative to the moment where it was created. Make sure you call
destroy
on the mapping when you're done with it.-
withMapping<T>(f: fn(mapping: WorkspaceMapping) → Promise<T>) → Promise<T>
Run the given promise with a workspace mapping active. Automatically release the mapping when the promise resolves or rejects.
-
sync()
Push any pending changes in the open files to the server. You'll want to call this before most types of requests, to make sure the server isn't working with outdated information.
-
-
type
LSPClientConfig Configuration options that can be passed to the LSP client.
-
rootUri?: string
The project root URI passed to the server, when necessary.
-
workspace?: fn(client: LSPClient) → Workspace
An optional function to create a workspace object for the client to use. When not given, this will default to a simple workspace that only opens files that have an active editor, and only allows one editor per file.
-
timeout?: number
The amount of milliseconds after which requests are automatically timed out. Defaults to 3000.
-
sanitizeHTML?: fn(html: string) → string
LSP servers can send Markdown code, which the client must render and display as HTML. Markdown can contain arbitrary HTML and is thus a potential channel for cross-site scripting attacks, if someone is able to compromise your LSP server or your connection to it. You can pass an HTML sanitizer here to strip out suspicious HTML structure.
-
highlightLanguage?: fn(name: string) → Language | null
By default, the Markdown renderer will only be able to highlght code embedded in the Markdown text when its language tag matches the name of the language used by the editor. You can provide a function here that returns a CodeMirror language object for a given language tag to support more languages.
-
notificationHandlers?: Object<fn(client: LSPClient, params: any) → boolean>
By default, the client will only handle the server notifications
window/logMessage
(logging warnings and errors to the console) andwindow/showMessage
. You can pass additional handlers here. They will be tried before the built-in handlers, and override those when they return true.-
unhandledNotification?: fn(client: LSPClient, method: string, params: any)
When no handler is found for a notification, it will be passed to this function, if given.
-
-
type
Transport An object of this type should be used to wrap whatever transport layer you use to talk to your language server. Messages should contain only the JSON messages, no LSP headers.
-
class
LSPPlugin A plugin that connects a given editor to a language server client.
-
client: LSPClient
The client connection.
-
uri: string
The URI of this file.
-
view: EditorView
The editor view that this plugin belongs to.
-
docToHTML(value: string | MarkupContent, defaultKind?: MarkupKind = "plaintext") → string
Render a doc string from the server to HTML.
-
toPosition(pos: number, doc?: Text = this.view.state.doc) → Position
Convert a CodeMirror document offset into an LSP
{line, character}
object. Defaults to using the view's current document, but can be given another one.-
fromPosition(pos: Position, doc?: Text = this.view.state.doc) → number
Convert an LSP
{line, character}
object to a CodeMirror document offset.-
reportError(message: string, err: any)
Display an error in this plugin's editor.
-
unsyncedChanges: ChangeSet
The changes accumulated in this editor that have not been sent to the server yet.
-
clear()
Reset the unsynced changes. Should probably only be called by a workspace.
-
static get(view: EditorView) → LSPPlugin | null
Get the LSP plugin associated with an editor, if any.
-
static create(client: LSPClient, fileURI: string, languageID?: string) → Extension
Create an editor extension that connects that editor to the given LSP client. This extension is necessary to use LSP-related functionality exported by this package. Creating an editor with this plugin will cause
openFile
to be called on the workspace.By default, the language ID given to the server for this file is derived from the editor's language configuration via
Language.name
. You can pass in a specific ID as a third parameter.
-
-
class
WorkspaceMapping A workspace mapping is used to track changes made to open documents, so that positions returned by a request can be interpreted in terms of the current, potentially changed document.
-
getMapping(uri: string) → ChangeDesc | null
Get the changes made to the document with the given URI since the mapping was created. Returns null for documents that aren't open.
-
mapPos(uri: string, pos: number, assoc?: number) → number
Map a position in the given file forward to the current document state.
-
mapPosition(uri: string, pos: Position, assoc?: number) → number
Convert an LSP-style position referring to a document at the time the mapping was created to an offset in the current document.
-
destroy()
Disconnect this mapping from the client so that it will no longer be notified of new changes. You must make sure to call this on every mapping you create, except when you use
withMapping
, which will automatically schedule a disconnect when the given promise resolves.
-
-
abstract class
Workspace Implementing your own workspace class can provide more control over the way files are loaded and managed when interacting with the language server. See
LSPClientConfig.workspace
.-
new Workspace(client: LSPClient)
The constructor, as called by the client when creating a workspace.
-
abstract files: WorkspaceFile[]
The files currently open in the workspace.
-
client: LSPClient
The LSP client associated with this workspace.
-
getFile(uri: string) → WorkspaceFile | null
Find the open file with the given URI, if it exists. The default implementation just looks it up in
this.files
.-
abstract syncFiles() → readonly {file: WorkspaceFile, prevDoc: Text, changes: ChangeSet}[]
Check all open files for changes (usually from editors, but they may also come from other sources). When a file is changed, return a record that describes the changes, and update the file's
version
anddoc
properties to reflect the new version.-
requestFile(uri: string) → Promise<WorkspaceFile | null>
Called to request that the workspace open a file. The default implementation simply returns the file if it is open, null otherwise.
-
abstract openFile(uri: string, languageId: string, view: EditorView)
Called when an editor is created for a file. The implementation should track the file in
this.files
and, if it wasn't open already, callLSPClient.didOpen
.-
abstract closeFile(uri: string, view: EditorView)
Called when an editor holding this file is destroyed or reconfigured to no longer hold it. The implementation should track this and, when it closes the file, make sure to call
LSPClient.didOpen
.-
connected()
Called when the client for this workspace is connected. The default implementation calls
LSPClient.didOpen
on all open files.-
disconnected()
Called when the client for this workspace is disconnected. The default implementation does nothing.
-
updateFile(uri: string, update: TransactionSpec)
Called when a server-initiated change to a file is applied. The default implementation simply dispatches the update to the file's view, if the file is open and has a view.
-
displayFile(uri: string) → Promise<EditorView | null>
When the client needs to put a file other than the one loaded in the current editor in front of the user, for example in
jumpToDefinition
, it will call this function. It should make sure to create or find an editor with the file and make it visible to the user, or return null if this isn't possible.
-
-
interface
WorkspaceFile A file that is open in a workspace.
-
uri: string
The file's unique URI.
-
languageId: string
The LSP language ID for the file's content.
-
version: number
The current version of the file.
-
doc: Text
The document corresponding to
this.version
. Will not reflect changes made after that version was synchronized. Will be updated, along withversion
, bysyncFiles
.-
getView(main?: EditorView) → EditorView | null
Get an active editor view for this file, if there is one. For workspaces that support multiple views on a file,
main
indicates a preferred view.
-
-
languageServerSupport(client: LSPClient, uri: string, languageID?: string) → Extension
Returns an extension that enables the LSP plugin and all other features provided by this package. You can also pick and choose individual extensions from the exports. In that case, make sure to also include
LSPPlugin.create
in your extensions, or the others will not work.-
serverCompletion(config?: Object = {}) → Extension
Register the language server completion source as an autocompletion source.
-
serverCompletionSource: CompletionSource
A completion source that requests completions from a language server.
-
hoverTooltips(config?: {hoverTime?: number} = {}) → Extension
Create an extension that queries the language server for hover tooltips when the user hovers over the code with their pointer, and displays a tooltip when the server provides one.
-
formatDocument: Command
This command asks the language server to reformat the document, and then applies the changes it returns.
-
formatKeymap: readonly KeyBinding[]
A keymap that binds Shift-Alt-f to
formatDocument
.-
renameSymbol: Command
This command will, if the cursor is over a word, prompt the user for a new name for that symbol, and ask the language server to perform a rename of that symbol.
Note that this may affect files other than the one loaded into this view. See the
Workspace.updateFile
method.-
renameKeymap: readonly KeyBinding[]
A keymap that binds F2 to
renameSymbol
.-
signatureHelp(config?: {keymap?: boolean} = {}) → Extension
Returns an extension that enables signature help. Will bind the keys in
signatureKeymap
unlesskeymap
is set tofalse
.-
showSignatureHelp: Command
Explicitly prompt the server to provide signature help at the cursor.
-
nextSignature: Command
If there is an active signature tooltip with multiple signatures, move to the next one.
-
prevSignature: Command
If there is an active signature tooltip with multiple signatures, move to the previous signature.
-
signatureKeymap: readonly KeyBinding[]
A keymap that binds
-
Ctrl-Shift-Space (Cmd-Shift-Space on macOS) to
showSignatureHelp
-
Ctrl-Shift-ArrowUp (Cmd-Shift-ArrowUp on macOS) to
prevSignature
-
Ctrl-Shift-ArrowDown (Cmd-Shift-ArrowDown on macOS) to
nextSignature
Note that these keys are automatically bound by
signatureHelp
unless you pass itkeymap: false
.-
-
jumpToDefinition: Command
Jump to the definition of the symbol at the cursor. To support cross-file jumps, you'll need to implement
Workspace.displayFile
.-
jumpToDeclaration: Command
Jump to the declaration of the symbol at the cursor.
-
jumpToTypeDefinition: Command
Jump to the type definition of the symbol at the cursor.
-
jumpToImplementation: Command
Jump to the implementation of the symbol at the cursor.
-
jumpToDefinitionKeymap: readonly KeyBinding[]
Binds F12 to
jumpToDefinition
.-
findReferences: Command
Ask the server to locate all references to the symbol at the cursor. When the server can provide such references, show them as a list in a panel.
-
closeReferencePanel: Command
Close the reference panel, if it is open.
-
findReferencesKeymap: readonly KeyBinding[]
Binds Shift-F12 to
findReferences
and Escape tocloseReferencePanel
.