aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/lsp
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim/lsp')
-rw-r--r--runtime/lua/vim/lsp/_meta/protocol.lua187
-rw-r--r--runtime/lua/vim/lsp/buf.lua242
-rw-r--r--runtime/lua/vim/lsp/client.lua95
-rw-r--r--runtime/lua/vim/lsp/codelens.lua18
-rw-r--r--runtime/lua/vim/lsp/handlers.lua111
-rw-r--r--runtime/lua/vim/lsp/health.lua23
-rw-r--r--runtime/lua/vim/lsp/inlay_hint.lua239
-rw-r--r--runtime/lua/vim/lsp/protocol.lua7
-rw-r--r--runtime/lua/vim/lsp/rpc.lua116
-rw-r--r--runtime/lua/vim/lsp/semantic_tokens.lua27
-rw-r--r--runtime/lua/vim/lsp/util.lua129
11 files changed, 655 insertions, 539 deletions
diff --git a/runtime/lua/vim/lsp/_meta/protocol.lua b/runtime/lua/vim/lsp/_meta/protocol.lua
index a5da5ac6b7..9a11972007 100644
--- a/runtime/lua/vim/lsp/_meta/protocol.lua
+++ b/runtime/lua/vim/lsp/_meta/protocol.lua
@@ -2534,8 +2534,14 @@ error('Cannot require a meta file')
---@proposed
---@field inlineCompletionProvider? boolean|lsp.InlineCompletionOptions
---
+---Text document specific server capabilities.
+---
+---@since 3.18.0
+---@proposed
+---@field textDocument? lsp._anonym12.textDocument
+---
---Workspace specific server capabilities.
----@field workspace? lsp._anonym12.workspace
+---@field workspace? lsp._anonym14.workspace
---
---Experimental server capabilities.
---@field experimental? lsp.LSPAny
@@ -2598,8 +2604,10 @@ error('Cannot require a meta file')
---appears in the user interface.
---@field source? string
---
----The diagnostic's message. It usually appears in the user interface
----@field message string
+---The diagnostic's message. It usually appears in the user interface.
+---
+---@since 3.18.0 - support for `MarkupContent`. This is guarded by the client capability `textDocument.diagnostic.markupMessageSupport`.
+---@field message string|lsp.MarkupContent
---
---Additional metadata about the diagnostic.
---
@@ -2684,7 +2692,7 @@ error('Cannot require a meta file')
---capabilities.
---
---@since 3.17.0
----@field completionItem? lsp._anonym13.completionItem
+---@field completionItem? lsp._anonym15.completionItem
---Hover options.
---@class lsp.HoverOptions: lsp.WorkDoneProgressOptions
@@ -2811,6 +2819,8 @@ error('Cannot require a meta file')
---errors are currently presented to the user for the given range. There is no guarantee
---that these accurately reflect the error state of the resource. The primary parameter
---to compute code actions is the provided range.
+---
+---Note that the client should check the `textDocument.diagnostic.markupMessageSupport` server capability before sending diagnostics with markup messages to a server.
---@field diagnostics lsp.Diagnostic[]
---
---Requested kind of actions to return.
@@ -3146,7 +3156,7 @@ error('Cannot require a meta file')
---@class lsp.NotebookDocumentSyncOptions
---
---The notebooks to be synced
----@field notebookSelector (lsp._anonym14.notebookSelector|lsp._anonym16.notebookSelector)[]
+---@field notebookSelector (lsp._anonym16.notebookSelector|lsp._anonym18.notebookSelector)[]
---
---Whether save notification should be forwarded to
---the server. Will only be honored if mode === `notebook`.
@@ -3514,7 +3524,7 @@ error('Cannot require a meta file')
---anymore since the information is outdated).
---
---@since 3.17.0
----@field staleRequestSupport? lsp._anonym18.staleRequestSupport
+---@field staleRequestSupport? lsp._anonym20.staleRequestSupport
---
---Client capabilities specific to regular expressions.
---
@@ -3590,7 +3600,7 @@ error('Cannot require a meta file')
---create file, rename file and delete file changes.
---
---@since 3.16.0
----@field changeAnnotationSupport? lsp._anonym19.changeAnnotationSupport
+---@field changeAnnotationSupport? lsp._anonym21.changeAnnotationSupport
---@class lsp.DidChangeConfigurationClientCapabilities
---
@@ -3617,20 +3627,20 @@ error('Cannot require a meta file')
---@field dynamicRegistration? boolean
---
---Specific capabilities for the `SymbolKind` in the `workspace/symbol` request.
----@field symbolKind? lsp._anonym20.symbolKind
+---@field symbolKind? lsp._anonym22.symbolKind
---
---The client supports tags on `SymbolInformation`.
---Clients supporting tags have to handle unknown tags gracefully.
---
---@since 3.16.0
----@field tagSupport? lsp._anonym21.tagSupport
+---@field tagSupport? lsp._anonym23.tagSupport
---
---The client support partial workspace symbols. The client will send the
---request `workspaceSymbol/resolve` to the server to resolve additional
---properties.
---
---@since 3.17.0
----@field resolveSupport? lsp._anonym22.resolveSupport
+---@field resolveSupport? lsp._anonym24.resolveSupport
---The client capabilities of a {@link ExecuteCommandRequest}.
---@class lsp.ExecuteCommandClientCapabilities
@@ -3775,9 +3785,9 @@ error('Cannot require a meta file')
---
---The client supports the following `CompletionItem` specific
---capabilities.
----@field completionItem? lsp._anonym23.completionItem
+---@field completionItem? lsp._anonym25.completionItem
---
----@field completionItemKind? lsp._anonym27.completionItemKind
+---@field completionItemKind? lsp._anonym29.completionItemKind
---
---Defines how the client handles whitespace and indentation
---when accepting a completion item that uses multi line
@@ -3794,7 +3804,7 @@ error('Cannot require a meta file')
---capabilities.
---
---@since 3.17.0
----@field completionList? lsp._anonym28.completionList
+---@field completionList? lsp._anonym30.completionList
---@class lsp.HoverClientCapabilities
---
@@ -3813,7 +3823,7 @@ error('Cannot require a meta file')
---
---The client supports the following `SignatureInformation`
---specific properties.
----@field signatureInformation? lsp._anonym29.signatureInformation
+---@field signatureInformation? lsp._anonym31.signatureInformation
---
---The client supports to send additional context information for a
---`textDocument/signatureHelp` request. A client that opts into
@@ -3891,7 +3901,7 @@ error('Cannot require a meta file')
---
---Specific capabilities for the `SymbolKind` in the
---`textDocument/documentSymbol` request.
----@field symbolKind? lsp._anonym31.symbolKind
+---@field symbolKind? lsp._anonym33.symbolKind
---
---The client supports hierarchical document symbols.
---@field hierarchicalDocumentSymbolSupport? boolean
@@ -3901,7 +3911,7 @@ error('Cannot require a meta file')
---Clients supporting tags have to handle unknown tags gracefully.
---
---@since 3.16.0
----@field tagSupport? lsp._anonym32.tagSupport
+---@field tagSupport? lsp._anonym34.tagSupport
---
---The client supports an additional label presented in the UI when
---registering a document symbol provider.
@@ -3920,7 +3930,7 @@ error('Cannot require a meta file')
---set the request can only return `Command` literals.
---
---@since 3.8.0
----@field codeActionLiteralSupport? lsp._anonym33.codeActionLiteralSupport
+---@field codeActionLiteralSupport? lsp._anonym35.codeActionLiteralSupport
---
---Whether code action supports the `isPreferred` property.
---
@@ -3943,7 +3953,7 @@ error('Cannot require a meta file')
---properties via a separate `codeAction/resolve` request.
---
---@since 3.16.0
----@field resolveSupport? lsp._anonym35.resolveSupport
+---@field resolveSupport? lsp._anonym37.resolveSupport
---
---Whether the client honors the change annotations in
---text edits and resource operations returned via the
@@ -4051,12 +4061,12 @@ error('Cannot require a meta file')
---Specific options for the folding range kind.
---
---@since 3.17.0
----@field foldingRangeKind? lsp._anonym36.foldingRangeKind
+---@field foldingRangeKind? lsp._anonym38.foldingRangeKind
---
---Specific options for the folding range.
---
---@since 3.17.0
----@field foldingRange? lsp._anonym37.foldingRange
+---@field foldingRange? lsp._anonym39.foldingRange
---@class lsp.SelectionRangeClientCapabilities
---
@@ -4075,7 +4085,7 @@ error('Cannot require a meta file')
---Clients supporting tags have to handle unknown tags gracefully.
---
---@since 3.15.0
----@field tagSupport? lsp._anonym38.tagSupport
+---@field tagSupport? lsp._anonym40.tagSupport
---
---Whether the client interprets the version property of the
---`textDocument/publishDiagnostics` notification's parameter.
@@ -4119,7 +4129,7 @@ error('Cannot require a meta file')
---`request.range` are both set to true but the server only provides a
---range provider the client might not render a minimap correctly or might
---even decide to not show any semantic tokens at all.
----@field requests lsp._anonym39.requests
+---@field requests lsp._anonym41.requests
---
---The token types that the client supports.
---@field tokenTypes string[]
@@ -4202,7 +4212,7 @@ error('Cannot require a meta file')
---
---Indicates which properties a client can resolve lazily on an inlay
---hint.
----@field resolveSupport? lsp._anonym42.resolveSupport
+---@field resolveSupport? lsp._anonym44.resolveSupport
---Client capabilities specific to diagnostic pull requests.
---
@@ -4216,6 +4226,9 @@ error('Cannot require a meta file')
---
---Whether the clients supports related documents for document diagnostic pulls.
---@field relatedDocumentSupport? boolean
+---
+---Whether the client supports `MarkupContent` in diagnostic messages.
+---@field markupMessageSupport? boolean
---Client capabilities specific to inline completions.
---
@@ -4244,7 +4257,7 @@ error('Cannot require a meta file')
---@class lsp.ShowMessageRequestClientCapabilities
---
---Capabilities specific to the `MessageActionItem` type.
----@field messageActionItem? lsp._anonym43.messageActionItem
+---@field messageActionItem? lsp._anonym45.messageActionItem
---Client capabilities for the showDocument request.
---
@@ -4671,7 +4684,7 @@ error('Cannot require a meta file')
---@since 3.17.0
---@alias lsp.DocumentDiagnosticReport lsp.RelatedFullDocumentDiagnosticReport|lsp.RelatedUnchangedDocumentDiagnosticReport
----@alias lsp.PrepareRenameResult lsp.Range|lsp._anonym44.PrepareRenameResult|lsp._anonym45.PrepareRenameResult
+---@alias lsp.PrepareRenameResult lsp.Range|lsp._anonym46.PrepareRenameResult|lsp._anonym47.PrepareRenameResult
---A document selector is the combination of one or many document filters.
---
@@ -4692,7 +4705,7 @@ error('Cannot require a meta file')
---An event describing a change to a text document. If only a text is provided
---it is considered to be the full content of the document.
----@alias lsp.TextDocumentContentChangeEvent lsp._anonym46.TextDocumentContentChangeEvent|lsp._anonym47.TextDocumentContentChangeEvent
+---@alias lsp.TextDocumentContentChangeEvent lsp._anonym48.TextDocumentContentChangeEvent|lsp._anonym49.TextDocumentContentChangeEvent
---MarkedString can be used to render human readable text. It is either a markdown string
---or a code-block that provides a language and a code snippet. The language identifier
@@ -4706,7 +4719,7 @@ error('Cannot require a meta file')
---
---Note that markdown strings will be sanitized - that means html will be escaped.
---@deprecated use MarkupContent instead.
----@alias lsp.MarkedString string|lsp._anonym48.MarkedString
+---@alias lsp.MarkedString string|lsp._anonym50.MarkedString
---A document filter describes a top level text document or
---a notebook cell document.
@@ -4739,14 +4752,14 @@ error('Cannot require a meta file')
---\@sample A language filter that applies to all package.json paths: `{ language: 'json', pattern: '**package.json' }`
---
---@since 3.17.0
----@alias lsp.TextDocumentFilter lsp._anonym49.TextDocumentFilter|lsp._anonym50.TextDocumentFilter|lsp._anonym51.TextDocumentFilter
+---@alias lsp.TextDocumentFilter lsp._anonym51.TextDocumentFilter|lsp._anonym52.TextDocumentFilter|lsp._anonym53.TextDocumentFilter
---A notebook document filter denotes a notebook document by
---different properties. The properties will be match
---against the notebook's URI (same as with documents)
---
---@since 3.17.0
----@alias lsp.NotebookDocumentFilter lsp._anonym52.NotebookDocumentFilter|lsp._anonym53.NotebookDocumentFilter|lsp._anonym54.NotebookDocumentFilter
+---@alias lsp.NotebookDocumentFilter lsp._anonym54.NotebookDocumentFilter|lsp._anonym55.NotebookDocumentFilter|lsp._anonym56.NotebookDocumentFilter
---The glob pattern to watch relative to the base path. Glob patterns can have the following syntax:
---- `*` to match one or more characters in a path segment
@@ -4856,7 +4869,19 @@ error('Cannot require a meta file')
---The client's version as defined by the client.
---@field version? string
----@class lsp._anonym12.workspace
+---@class lsp._anonym13.textDocument.diagnostic
+---
+---Whether the server supports `MarkupContent` in diagnostic messages.
+---@field markupMessageSupport? boolean
+
+---@class lsp._anonym12.textDocument
+---
+---Capabilities specific to the diagnostic pull model.
+---
+---@since 3.18.0
+---@field diagnostic? lsp._anonym13.textDocument.diagnostic
+
+---@class lsp._anonym14.workspace
---
---The server supports workspace folder.
---
@@ -4868,7 +4893,7 @@ error('Cannot require a meta file')
---@since 3.16.0
---@field fileOperations? lsp.FileOperationOptions
----@class lsp._anonym13.completionItem
+---@class lsp._anonym15.completionItem
---
---The server has support for completion item label
---details (see also `CompletionItemLabelDetails`) when
@@ -4877,11 +4902,11 @@ error('Cannot require a meta file')
---@since 3.17.0
---@field labelDetailsSupport? boolean
----@class lsp._anonym15.notebookSelector.cells
+---@class lsp._anonym17.notebookSelector.cells
---
---@field language string
----@class lsp._anonym14.notebookSelector
+---@class lsp._anonym16.notebookSelector
---
---The notebook to be synced If a string
---value is provided it matches against the
@@ -4889,13 +4914,13 @@ error('Cannot require a meta file')
---@field notebook string|lsp.NotebookDocumentFilter
---
---The cells of the matching notebook to be synced.
----@field cells? lsp._anonym15.notebookSelector.cells[]
+---@field cells? lsp._anonym17.notebookSelector.cells[]
----@class lsp._anonym17.notebookSelector.cells
+---@class lsp._anonym19.notebookSelector.cells
---
---@field language string
----@class lsp._anonym16.notebookSelector
+---@class lsp._anonym18.notebookSelector
---
---The notebook to be synced If a string
---value is provided it matches against the
@@ -4903,9 +4928,9 @@ error('Cannot require a meta file')
---@field notebook? string|lsp.NotebookDocumentFilter
---
---The cells of the matching notebook to be synced.
----@field cells lsp._anonym17.notebookSelector.cells[]
+---@field cells lsp._anonym19.notebookSelector.cells[]
----@class lsp._anonym18.staleRequestSupport
+---@class lsp._anonym20.staleRequestSupport
---
---The client will actively cancel the request.
---@field cancel boolean
@@ -4915,14 +4940,14 @@ error('Cannot require a meta file')
---response with error code `ContentModified`
---@field retryOnContentModified string[]
----@class lsp._anonym19.changeAnnotationSupport
+---@class lsp._anonym21.changeAnnotationSupport
---
---Whether the client groups edits with equal labels into tree nodes,
---for instance all edits labelled with "Changes in Strings" would
---be a tree node.
---@field groupsOnLabel? boolean
----@class lsp._anonym20.symbolKind
+---@class lsp._anonym22.symbolKind
---
---The symbol kind values the client supports. When this
---property exists the client also guarantees that it will
@@ -4934,32 +4959,32 @@ error('Cannot require a meta file')
---the initial version of the protocol.
---@field valueSet? lsp.SymbolKind[]
----@class lsp._anonym21.tagSupport
+---@class lsp._anonym23.tagSupport
---
---The tags supported by the client.
---@field valueSet lsp.SymbolTag[]
----@class lsp._anonym22.resolveSupport
+---@class lsp._anonym24.resolveSupport
---
---The properties that a client can resolve lazily. Usually
---`location.range`
---@field properties string[]
----@class lsp._anonym24.completionItem.tagSupport
+---@class lsp._anonym26.completionItem.tagSupport
---
---The tags supported by the client.
---@field valueSet lsp.CompletionItemTag[]
----@class lsp._anonym25.completionItem.resolveSupport
+---@class lsp._anonym27.completionItem.resolveSupport
---
---The properties that a client can resolve lazily.
---@field properties string[]
----@class lsp._anonym26.completionItem.insertTextModeSupport
+---@class lsp._anonym28.completionItem.insertTextModeSupport
---
---@field valueSet lsp.InsertTextMode[]
----@class lsp._anonym23.completionItem
+---@class lsp._anonym25.completionItem
---
---Client supports snippets as insert text.
---
@@ -4988,7 +5013,7 @@ error('Cannot require a meta file')
---a resolve call.
---
---@since 3.15.0
----@field tagSupport? lsp._anonym24.completionItem.tagSupport
+---@field tagSupport? lsp._anonym26.completionItem.tagSupport
---
---Client support insert replace edit to control different behavior if a
---completion item is inserted in the text or should replace text.
@@ -5001,14 +5026,14 @@ error('Cannot require a meta file')
---and `details` could be resolved lazily.
---
---@since 3.16.0
----@field resolveSupport? lsp._anonym25.completionItem.resolveSupport
+---@field resolveSupport? lsp._anonym27.completionItem.resolveSupport
---
---The client supports the `insertTextMode` property on
---a completion item to override the whitespace handling mode
---as defined by the client (see `insertTextMode`).
---
---@since 3.16.0
----@field insertTextModeSupport? lsp._anonym26.completionItem.insertTextModeSupport
+---@field insertTextModeSupport? lsp._anonym28.completionItem.insertTextModeSupport
---
---The client has support for completion item label
---details (see also `CompletionItemLabelDetails`).
@@ -5016,7 +5041,7 @@ error('Cannot require a meta file')
---@since 3.17.0
---@field labelDetailsSupport? boolean
----@class lsp._anonym27.completionItemKind
+---@class lsp._anonym29.completionItemKind
---
---The completion item kind values the client supports. When this
---property exists the client also guarantees that it will
@@ -5028,7 +5053,7 @@ error('Cannot require a meta file')
---the initial version of the protocol.
---@field valueSet? lsp.CompletionItemKind[]
----@class lsp._anonym28.completionList
+---@class lsp._anonym30.completionList
---
---The client supports the following itemDefaults on
---a completion list.
@@ -5040,7 +5065,7 @@ error('Cannot require a meta file')
---@since 3.17.0
---@field itemDefaults? string[]
----@class lsp._anonym30.signatureInformation.parameterInformation
+---@class lsp._anonym32.signatureInformation.parameterInformation
---
---The client supports processing label offsets instead of a
---simple label string.
@@ -5048,14 +5073,14 @@ error('Cannot require a meta file')
---@since 3.14.0
---@field labelOffsetSupport? boolean
----@class lsp._anonym29.signatureInformation
+---@class lsp._anonym31.signatureInformation
---
---Client supports the following content formats for the documentation
---property. The order describes the preferred format of the client.
---@field documentationFormat? lsp.MarkupKind[]
---
---Client capabilities specific to parameter information.
----@field parameterInformation? lsp._anonym30.signatureInformation.parameterInformation
+---@field parameterInformation? lsp._anonym32.signatureInformation.parameterInformation
---
---The client supports the `activeParameter` property on `SignatureInformation`
---literal.
@@ -5070,7 +5095,7 @@ error('Cannot require a meta file')
---@since 3.18.0
---@field noActiveParameterSupport? boolean
----@class lsp._anonym31.symbolKind
+---@class lsp._anonym33.symbolKind
---
---The symbol kind values the client supports. When this
---property exists the client also guarantees that it will
@@ -5082,12 +5107,12 @@ error('Cannot require a meta file')
---the initial version of the protocol.
---@field valueSet? lsp.SymbolKind[]
----@class lsp._anonym32.tagSupport
+---@class lsp._anonym34.tagSupport
---
---The tags supported by the client.
---@field valueSet lsp.SymbolTag[]
----@class lsp._anonym34.codeActionLiteralSupport.codeActionKind
+---@class lsp._anonym36.codeActionLiteralSupport.codeActionKind
---
---The code action kind values the client supports. When this
---property exists the client also guarantees that it will
@@ -5095,18 +5120,18 @@ error('Cannot require a meta file')
---to a default value when unknown.
---@field valueSet lsp.CodeActionKind[]
----@class lsp._anonym33.codeActionLiteralSupport
+---@class lsp._anonym35.codeActionLiteralSupport
---
---The code action kind is support with the following value
---set.
----@field codeActionKind lsp._anonym34.codeActionLiteralSupport.codeActionKind
+---@field codeActionKind lsp._anonym36.codeActionLiteralSupport.codeActionKind
----@class lsp._anonym35.resolveSupport
+---@class lsp._anonym37.resolveSupport
---
---The properties that a client can resolve lazily.
---@field properties string[]
----@class lsp._anonym36.foldingRangeKind
+---@class lsp._anonym38.foldingRangeKind
---
---The folding range kind values the client supports. When this
---property exists the client also guarantees that it will
@@ -5114,7 +5139,7 @@ error('Cannot require a meta file')
---to a default value when unknown.
---@field valueSet? lsp.FoldingRangeKind[]
----@class lsp._anonym37.foldingRange
+---@class lsp._anonym39.foldingRange
---
---If set, the client signals that it supports setting collapsedText on
---folding ranges to display custom labels instead of the default text.
@@ -5122,52 +5147,52 @@ error('Cannot require a meta file')
---@since 3.17.0
---@field collapsedText? boolean
----@class lsp._anonym38.tagSupport
+---@class lsp._anonym40.tagSupport
---
---The tags supported by the client.
---@field valueSet lsp.DiagnosticTag[]
----@class lsp._anonym40.requests.range
+---@class lsp._anonym42.requests.range
----@class lsp._anonym41.requests.full
+---@class lsp._anonym43.requests.full
---
---The client will send the `textDocument/semanticTokens/full/delta` request if
---the server provides a corresponding handler.
---@field delta? boolean
----@class lsp._anonym39.requests
+---@class lsp._anonym41.requests
---
---The client will send the `textDocument/semanticTokens/range` request if
---the server provides a corresponding handler.
----@field range? boolean|lsp._anonym40.requests.range
+---@field range? boolean|lsp._anonym42.requests.range
---
---The client will send the `textDocument/semanticTokens/full` request if
---the server provides a corresponding handler.
----@field full? boolean|lsp._anonym41.requests.full
+---@field full? boolean|lsp._anonym43.requests.full
----@class lsp._anonym42.resolveSupport
+---@class lsp._anonym44.resolveSupport
---
---The properties that a client can resolve lazily.
---@field properties string[]
----@class lsp._anonym43.messageActionItem
+---@class lsp._anonym45.messageActionItem
---
---Whether the client supports additional attributes which
---are preserved and send back to the server in the
---request's response.
---@field additionalPropertiesSupport? boolean
----@class lsp._anonym44.PrepareRenameResult
+---@class lsp._anonym46.PrepareRenameResult
---
---@field range lsp.Range
---
---@field placeholder string
----@class lsp._anonym45.PrepareRenameResult
+---@class lsp._anonym47.PrepareRenameResult
---
---@field defaultBehavior boolean
----@class lsp._anonym46.TextDocumentContentChangeEvent
+---@class lsp._anonym48.TextDocumentContentChangeEvent
---
---The range of the document that changed.
---@field range lsp.Range
@@ -5180,18 +5205,18 @@ error('Cannot require a meta file')
---The new text for the provided range.
---@field text string
----@class lsp._anonym47.TextDocumentContentChangeEvent
+---@class lsp._anonym49.TextDocumentContentChangeEvent
---
---The new text of the whole document.
---@field text string
----@class lsp._anonym48.MarkedString
+---@class lsp._anonym50.MarkedString
---
---@field language string
---
---@field value string
----@class lsp._anonym49.TextDocumentFilter
+---@class lsp._anonym51.TextDocumentFilter
---
---A language id, like `typescript`.
---@field language string
@@ -5202,7 +5227,7 @@ error('Cannot require a meta file')
---A glob pattern, like **/*.{ts,js}. See TextDocumentFilter for examples.
---@field pattern? string
----@class lsp._anonym50.TextDocumentFilter
+---@class lsp._anonym52.TextDocumentFilter
---
---A language id, like `typescript`.
---@field language? string
@@ -5213,7 +5238,7 @@ error('Cannot require a meta file')
---A glob pattern, like **/*.{ts,js}. See TextDocumentFilter for examples.
---@field pattern? string
----@class lsp._anonym51.TextDocumentFilter
+---@class lsp._anonym53.TextDocumentFilter
---
---A language id, like `typescript`.
---@field language? string
@@ -5224,7 +5249,7 @@ error('Cannot require a meta file')
---A glob pattern, like **/*.{ts,js}. See TextDocumentFilter for examples.
---@field pattern string
----@class lsp._anonym52.NotebookDocumentFilter
+---@class lsp._anonym54.NotebookDocumentFilter
---
---The type of the enclosing notebook.
---@field notebookType string
@@ -5235,7 +5260,7 @@ error('Cannot require a meta file')
---A glob pattern.
---@field pattern? string
----@class lsp._anonym53.NotebookDocumentFilter
+---@class lsp._anonym55.NotebookDocumentFilter
---
---The type of the enclosing notebook.
---@field notebookType? string
@@ -5246,7 +5271,7 @@ error('Cannot require a meta file')
---A glob pattern.
---@field pattern? string
----@class lsp._anonym54.NotebookDocumentFilter
+---@class lsp._anonym56.NotebookDocumentFilter
---
---The type of the enclosing notebook.
---@field notebookType? string
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index 50121f30b2..49833eaeec 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -35,13 +35,13 @@ function M.hover()
request(ms.textDocument_hover, params)
end
-local function request_with_options(name, params, options)
+local function request_with_opts(name, params, opts)
local req_handler --- @type function?
- if options then
+ if opts then
req_handler = function(err, result, ctx, config)
local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
local handler = client.handlers[name] or vim.lsp.handlers[name]
- handler(err, result, ctx, vim.tbl_extend('force', config or {}, options))
+ handler(err, result, ctx, vim.tbl_extend('force', config or {}, opts))
end
end
request(name, params, req_handler)
@@ -62,14 +62,13 @@ end
--- vim.lsp.buf.references(nil, { on_list = on_list })
--- ```
---
---- If you prefer loclist do something like this:
+--- If you prefer loclist instead of qflist:
--- ```lua
---- local function on_list(options)
---- vim.fn.setloclist(0, {}, ' ', options)
---- vim.cmd.lopen()
---- end
+--- vim.lsp.buf.definition({ loclist = true })
+--- vim.lsp.buf.references(nil, { loclist = true })
--- ```
--- @field on_list? fun(t: vim.lsp.LocationOpts.OnList)
+--- @field loclist? boolean
--- @class vim.lsp.LocationOpts.OnList
--- @field items table[] Structured like |setqflist-what|
@@ -83,32 +82,32 @@ end
--- Jumps to the declaration of the symbol under the cursor.
--- @note Many servers do not implement this method. Generally, see |vim.lsp.buf.definition()| instead.
---- @param options? vim.lsp.LocationOpts
-function M.declaration(options)
+--- @param opts? vim.lsp.LocationOpts
+function M.declaration(opts)
local params = util.make_position_params()
- request_with_options(ms.textDocument_declaration, params, options)
+ request_with_opts(ms.textDocument_declaration, params, opts)
end
--- Jumps to the definition of the symbol under the cursor.
---- @param options? vim.lsp.LocationOpts
-function M.definition(options)
+--- @param opts? vim.lsp.LocationOpts
+function M.definition(opts)
local params = util.make_position_params()
- request_with_options(ms.textDocument_definition, params, options)
+ request_with_opts(ms.textDocument_definition, params, opts)
end
--- Jumps to the definition of the type of the symbol under the cursor.
---- @param options? vim.lsp.LocationOpts
-function M.type_definition(options)
+--- @param opts? vim.lsp.LocationOpts
+function M.type_definition(opts)
local params = util.make_position_params()
- request_with_options(ms.textDocument_typeDefinition, params, options)
+ request_with_opts(ms.textDocument_typeDefinition, params, opts)
end
--- Lists all the implementations for the symbol under the cursor in the
--- quickfix window.
---- @param options? vim.lsp.LocationOpts
-function M.implementation(options)
+--- @param opts? vim.lsp.LocationOpts
+function M.implementation(opts)
local params = util.make_position_params()
- request_with_options(ms.textDocument_implementation, params, options)
+ request_with_opts(ms.textDocument_implementation, params, opts)
end
--- Displays signature information about the symbol under the cursor in a
@@ -213,25 +212,25 @@ end
--- Formats a buffer using the attached (and optionally filtered) language
--- server clients.
---
---- @param options? vim.lsp.buf.format.Opts
-function M.format(options)
- options = options or {}
- local bufnr = options.bufnr or api.nvim_get_current_buf()
+--- @param opts? vim.lsp.buf.format.Opts
+function M.format(opts)
+ opts = opts or {}
+ local bufnr = opts.bufnr or api.nvim_get_current_buf()
local mode = api.nvim_get_mode().mode
- local range = options.range
+ local range = opts.range
if not range and mode == 'v' or mode == 'V' then
range = range_from_selection(bufnr, mode)
end
local method = range and ms.textDocument_rangeFormatting or ms.textDocument_formatting
local clients = vim.lsp.get_clients({
- id = options.id,
+ id = opts.id,
bufnr = bufnr,
- name = options.name,
+ name = opts.name,
method = method,
})
- if options.filter then
- clients = vim.tbl_filter(options.filter, clients)
+ if opts.filter then
+ clients = vim.tbl_filter(opts.filter, clients)
end
if #clients == 0 then
@@ -250,12 +249,12 @@ function M.format(options)
return params
end
- if options.async then
+ if opts.async then
local function do_format(idx, client)
if not client then
return
end
- local params = set_range(client, util.make_formatting_params(options.formatting_options))
+ local params = set_range(client, util.make_formatting_params(opts.formatting_options))
client.request(method, params, function(...)
local handler = client.handlers[method] or vim.lsp.handlers[method]
handler(...)
@@ -264,9 +263,9 @@ function M.format(options)
end
do_format(next(clients))
else
- local timeout_ms = options.timeout_ms or 1000
+ local timeout_ms = opts.timeout_ms or 1000
for _, client in pairs(clients) do
- local params = set_range(client, util.make_formatting_params(options.formatting_options))
+ local params = set_range(client, util.make_formatting_params(opts.formatting_options))
local result, err = client.request_sync(method, params, timeout_ms, bufnr)
if result and result.result then
util.apply_text_edits(result.result, bufnr, client.offset_encoding)
@@ -295,18 +294,18 @@ end
---
---@param new_name string|nil If not provided, the user will be prompted for a new
--- name using |vim.ui.input()|.
----@param options? vim.lsp.buf.rename.Opts Additional options:
-function M.rename(new_name, options)
- options = options or {}
- local bufnr = options.bufnr or api.nvim_get_current_buf()
+---@param opts? vim.lsp.buf.rename.Opts Additional options:
+function M.rename(new_name, opts)
+ opts = opts or {}
+ local bufnr = opts.bufnr or api.nvim_get_current_buf()
local clients = vim.lsp.get_clients({
bufnr = bufnr,
- name = options.name,
+ name = opts.name,
-- Clients must at least support rename, prepareRename is optional
method = ms.textDocument_rename,
})
- if options.filter then
- clients = vim.tbl_filter(options.filter, clients)
+ if opts.filter then
+ clients = vim.tbl_filter(opts.filter, clients)
end
if #clients == 0 then
@@ -415,21 +414,21 @@ end
---
---@param context (table|nil) Context for the request
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
----@param options? vim.lsp.ListOpts
-function M.references(context, options)
+---@param opts? vim.lsp.ListOpts
+function M.references(context, opts)
validate({ context = { context, 't', true } })
local params = util.make_position_params()
params.context = context or {
includeDeclaration = true,
}
- request_with_options(ms.textDocument_references, params, options)
+ request_with_opts(ms.textDocument_references, params, opts)
end
--- Lists all symbols in the current buffer in the quickfix window.
---- @param options? vim.lsp.ListOpts
-function M.document_symbol(options)
+--- @param opts? vim.lsp.ListOpts
+function M.document_symbol(opts)
local params = { textDocument = util.make_text_document_params() }
- request_with_options(ms.textDocument_documentSymbol, params, options)
+ request_with_opts(ms.textDocument_documentSymbol, params, opts)
end
--- @param call_hierarchy_items lsp.CallHierarchyItem[]?
@@ -461,7 +460,14 @@ local function call_hierarchy(method)
vim.notify(err.message, vim.log.levels.WARN)
return
end
+ if not result then
+ vim.notify('No item resolved', vim.log.levels.WARN)
+ return
+ end
local call_hierarchy_item = pick_call_hierarchy_item(result)
+ if not call_hierarchy_item then
+ return
+ end
local client = vim.lsp.get_client_by_id(ctx.client_id)
if client then
client.request(method, { item = call_hierarchy_item }, nil, ctx.bufnr)
@@ -488,6 +494,80 @@ function M.outgoing_calls()
call_hierarchy(ms.callHierarchy_outgoingCalls)
end
+--- Lists all the subtypes or supertypes of the symbol under the
+--- cursor in the |quickfix| window. If the symbol can resolve to
+--- multiple items, the user can pick one using |vim.ui.select()|.
+---@param kind "subtypes"|"supertypes"
+function M.typehierarchy(kind)
+ local method = kind == 'subtypes' and ms.typeHierarchy_subtypes or ms.typeHierarchy_supertypes
+
+ --- Merge results from multiple clients into a single table. Client-ID is preserved.
+ ---
+ --- @param results table<integer, {error: lsp.ResponseError?, result: lsp.TypeHierarchyItem[]?}>
+ --- @return [integer, lsp.TypeHierarchyItem][]
+ local function merge_results(results)
+ local merged_results = {}
+ for client_id, client_result in pairs(results) do
+ if client_result.error then
+ vim.notify(client_result.error.message, vim.log.levels.WARN)
+ elseif client_result.result then
+ for _, item in pairs(client_result.result) do
+ table.insert(merged_results, { client_id, item })
+ end
+ end
+ end
+ return merged_results
+ end
+
+ local bufnr = api.nvim_get_current_buf()
+ local params = util.make_position_params()
+ --- @param results table<integer, {error: lsp.ResponseError?, result: lsp.TypeHierarchyItem[]?}>
+ vim.lsp.buf_request_all(bufnr, ms.textDocument_prepareTypeHierarchy, params, function(results)
+ local merged_results = merge_results(results)
+ if #merged_results == 0 then
+ vim.notify('No items resolved', vim.log.levels.INFO)
+ return
+ end
+
+ if #merged_results == 1 then
+ local item = merged_results[1]
+ local client = vim.lsp.get_client_by_id(item[1])
+ if client then
+ client.request(method, { item = item[2] }, nil, bufnr)
+ else
+ vim.notify(
+ string.format('Client with id=%d disappeared during call hierarchy request', item[1]),
+ vim.log.levels.WARN
+ )
+ end
+ else
+ local select_opts = {
+ prompt = 'Select a type hierarchy item:',
+ kind = 'typehierarchy',
+ format_item = function(item)
+ if not item[2].detail or #item[2].detail == 0 then
+ return item[2].name
+ end
+ return string.format('%s %s', item[2].name, item[2].detail)
+ end,
+ }
+
+ vim.ui.select(merged_results, select_opts, function(item)
+ local client = vim.lsp.get_client_by_id(item[1])
+ if client then
+ --- @type lsp.TypeHierarchyItem
+ client.request(method, { item = item[2] }, nil, bufnr)
+ else
+ vim.notify(
+ string.format('Client with id=%d disappeared during call hierarchy request', item[1]),
+ vim.log.levels.WARN
+ )
+ end
+ end)
+ end
+ end)
+end
+
--- List workspace folders.
---
function M.list_workspace_folders()
@@ -514,28 +594,9 @@ function M.add_workspace_folder(workspace_folder)
print(workspace_folder, ' is not a valid directory')
return
end
- local new_workspace = {
- uri = vim.uri_from_fname(workspace_folder),
- name = workspace_folder,
- }
- local params = { event = { added = { new_workspace }, removed = {} } }
- local bufnr = vim.api.nvim_get_current_buf()
+ local bufnr = api.nvim_get_current_buf()
for _, client in pairs(vim.lsp.get_clients({ bufnr = bufnr })) do
- local found = false
- for _, folder in pairs(client.workspace_folders or {}) do
- if folder.name == workspace_folder then
- found = true
- print(workspace_folder, 'is already part of this workspace')
- break
- end
- end
- if not found then
- client.notify(ms.workspace_didChangeWorkspaceFolders, params)
- if not client.workspace_folders then
- client.workspace_folders = {}
- end
- table.insert(client.workspace_folders, new_workspace)
- end
+ client:_add_workspace_folder(workspace_folder)
end
end
@@ -547,23 +608,12 @@ function M.remove_workspace_folder(workspace_folder)
workspace_folder = workspace_folder
or npcall(vim.fn.input, 'Workspace Folder: ', vim.fn.expand('%:p:h'))
api.nvim_command('redraw')
- if not (workspace_folder and #workspace_folder > 0) then
+ if not workspace_folder or #workspace_folder == 0 then
return
end
- local workspace = {
- uri = vim.uri_from_fname(workspace_folder),
- name = workspace_folder,
- }
- local params = { event = { added = {}, removed = { workspace } } }
- local bufnr = vim.api.nvim_get_current_buf()
+ local bufnr = api.nvim_get_current_buf()
for _, client in pairs(vim.lsp.get_clients({ bufnr = bufnr })) do
- for idx, folder in pairs(client.workspace_folders) do
- if folder.name == workspace_folder then
- client.notify(ms.workspace_didChangeWorkspaceFolders, params)
- client.workspace_folders[idx] = nil
- return
- end
- end
+ client:_remove_workspace_folder(workspace_folder)
end
print(workspace_folder, 'is not currently part of the workspace')
end
@@ -575,14 +625,14 @@ end
--- string means no filtering is done.
---
--- @param query string? optional
---- @param options? vim.lsp.ListOpts
-function M.workspace_symbol(query, options)
+--- @param opts? vim.lsp.ListOpts
+function M.workspace_symbol(query, opts)
query = query or npcall(vim.fn.input, 'Query: ')
if query == nil then
return
end
local params = { query = query }
- request_with_options(ms.workspace_symbol, params, options)
+ request_with_opts(ms.workspace_symbol, params, opts)
end
--- Send request to the server to resolve document highlights for the current
@@ -774,19 +824,19 @@ end
--- Selects a code action available at the current
--- cursor position.
---
----@param options? vim.lsp.buf.code_action.Opts
+---@param opts? vim.lsp.buf.code_action.Opts
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
---@see vim.lsp.protocol.CodeActionTriggerKind
-function M.code_action(options)
- validate({ options = { options, 't', true } })
- options = options or {}
+function M.code_action(opts)
+ validate({ options = { opts, 't', true } })
+ opts = opts or {}
-- Detect old API call code_action(context) which should now be
-- code_action({ context = context} )
--- @diagnostic disable-next-line:undefined-field
- if options.diagnostics or options.only then
- options = { options = options }
+ if opts.diagnostics or opts.only then
+ opts = { options = opts }
end
- local context = options.context or {}
+ local context = opts.context or {}
if not context.triggerKind then
context.triggerKind = vim.lsp.protocol.CodeActionTriggerKind.Invoked
end
@@ -816,17 +866,17 @@ function M.code_action(options)
results[ctx.client_id] = { error = err, result = result, ctx = ctx }
remaining = remaining - 1
if remaining == 0 then
- on_code_action_results(results, options)
+ on_code_action_results(results, opts)
end
end
for _, client in ipairs(clients) do
---@type lsp.CodeActionParams
local params
- if options.range then
- assert(type(options.range) == 'table', 'code_action range must be a table')
- local start = assert(options.range.start, 'range must have a `start` property')
- local end_ = assert(options.range['end'], 'range must have a `end` property')
+ if opts.range then
+ assert(type(opts.range) == 'table', 'code_action range must be a table')
+ local start = assert(opts.range.start, 'range must have a `start` property')
+ local end_ = assert(opts.range['end'], 'range must have a `end` property')
params = util.make_given_range_params(start, end_, bufnr, client.offset_encoding)
elseif mode == 'v' or mode == 'V' then
local range = range_from_selection(bufnr, mode)
diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua
index ff0db166d5..4beb7fefda 100644
--- a/runtime/lua/vim/lsp/client.lua
+++ b/runtime/lua/vim/lsp/client.lua
@@ -15,7 +15,7 @@ local validate = vim.validate
--- @inlinedoc
---
--- Allow using incremental sync for buffer edits
---- (defailt: `true`)
+--- (default: `true`)
--- @field allow_incremental_sync? boolean
---
--- Debounce `didChange` notifications to the server by the given number in milliseconds.
@@ -37,7 +37,7 @@ local validate = vim.validate
--- `is_closing` and `terminate`.
--- See |vim.lsp.rpc.request()|, |vim.lsp.rpc.notify()|.
--- For TCP there is a builtin RPC client factory: |vim.lsp.rpc.connect()|
---- @field cmd string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient?
+--- @field cmd string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient
---
--- Directory to launch the `cmd` process. Not related to `root_dir`.
--- (default: cwd)
@@ -185,6 +185,10 @@ local validate = vim.validate
--- @field root_dir string
---
--- @field attached_buffers table<integer,true>
+---
+--- Buffers that should be attached to upon initialize()
+--- @field package _buffers_to_attach table<integer,true>
+---
--- @field private _log_prefix string
---
--- Track this so that we can escalate automatically if we've already tried a
@@ -225,7 +229,7 @@ local validate = vim.validate
--- If {status} is `true`, the function returns {request_id} as the second
--- result. You can use this with `client.cancel_request(request_id)` to cancel
--- the request.
---- @field request fun(method: string, params: table?, handler: lsp.Handler?, bufnr: integer): boolean, integer?
+--- @field request fun(method: string, params: table?, handler: lsp.Handler?, bufnr: integer?): boolean, integer?
---
--- Sends a request to the server and synchronously waits for the response.
--- This is a wrapper around {client.request}
@@ -416,7 +420,7 @@ local function get_workspace_folders(workspace_folders, root_dir)
return {
{
uri = vim.uri_from_fname(root_dir),
- name = string.format('%s', root_dir),
+ name = root_dir,
},
}
end
@@ -502,25 +506,17 @@ function Client.create(config)
}
-- Start the RPC client.
- local rpc --- @type vim.lsp.rpc.PublicClient?
local config_cmd = config.cmd
if type(config_cmd) == 'function' then
- rpc = config_cmd(dispatchers)
+ self.rpc = config_cmd(dispatchers)
else
- rpc = lsp.rpc.start(config_cmd, dispatchers, {
+ self.rpc = lsp.rpc.start(config_cmd, dispatchers, {
cwd = config.cmd_cwd,
env = config.cmd_env,
detached = config.detached,
})
end
- -- Return nil if the rpc client fails to start
- if not rpc then
- return
- end
-
- self.rpc = rpc
-
setmetatable(self, Client)
return self
@@ -575,6 +571,7 @@ function Client:initialize()
initializationOptions = config.init_options,
capabilities = self.capabilities,
trace = self._trace,
+ workDoneToken = '1',
}
self:_run_callbacks(
@@ -608,8 +605,19 @@ function Client:initialize()
self:_notify(ms.workspace_didChangeConfiguration, { settings = self.settings })
end
+ -- If server is being restarted, make sure to re-attach to any previously attached buffers.
+ -- Save which buffers before on_init in case new buffers are attached.
+ local reattach_bufs = vim.deepcopy(self.attached_buffers)
+
self:_run_callbacks(self._on_init_cbs, lsp.client_errors.ON_INIT_CALLBACK_ERROR, self, result)
+ for buf in pairs(reattach_bufs) do
+ -- The buffer may have been detached in the on_init callback.
+ if self.attached_buffers[buf] then
+ self:_on_attach(buf)
+ end
+ end
+
log.info(
self._log_prefix,
'server_capabilities',
@@ -647,10 +655,10 @@ end
--- checks for capabilities and handler availability.
---
--- @param method string LSP method name.
---- @param params table|nil LSP request params.
---- @param handler lsp.Handler|nil Response |lsp-handler| for this method.
---- @param bufnr integer Buffer handle (0 for current).
---- @return boolean status, integer|nil request_id {status} is a bool indicating
+--- @param params? table LSP request params.
+--- @param handler? lsp.Handler Response |lsp-handler| for this method.
+--- @param bufnr? integer Buffer handle (0 for current).
+--- @return boolean status, integer? request_id {status} is a bool indicating
--- whether the request was successful. If it is `false`, then it will
--- always be `false` (the client has shutdown). If it was
--- successful, then it will return {request_id} as the
@@ -693,7 +701,7 @@ function Client:_request(method, params, handler, bufnr)
local request = { type = 'pending', bufnr = bufnr, method = method }
self.requests[request_id] = request
api.nvim_exec_autocmds('LspRequest', {
- buffer = bufnr,
+ buffer = api.nvim_buf_is_valid(bufnr) and bufnr or nil,
modeline = false,
data = { client_id = self.id, request_id = request_id, request = request },
})
@@ -709,7 +717,7 @@ local wait_result_reason = { [-1] = 'timeout', [-2] = 'interrupted', [-3] = 'err
---
--- @param ... string List to write to the buffer
local function err_message(...)
- local message = table.concat(vim.tbl_flatten({ ... }))
+ local message = table.concat(vim.iter({ ... }):flatten():totable())
if vim.in_fast_event() then
vim.schedule(function()
api.nvim_err_writeln(message)
@@ -761,7 +769,7 @@ function Client:_request_sync(method, params, timeout_ms, bufnr)
return request_result
end
---- @private
+--- @package
--- Sends a notification to an LSP server.
---
--- @param method string LSP method name.
@@ -804,7 +812,7 @@ function Client:_cancel_request(id)
if request and request.type == 'pending' then
request.type = 'cancel'
api.nvim_exec_autocmds('LspRequest', {
- buffer = request.bufnr,
+ buffer = api.nvim_buf_is_valid(request.bufnr) and request.bufnr or nil,
modeline = false,
data = { client_id = self.id, request_id = id, request = request },
})
@@ -900,7 +908,7 @@ end
--- @param bufnr integer Number of the buffer, or 0 for current
function Client:_text_document_did_open_handler(bufnr)
changetracking.init(self, bufnr)
- if not vim.tbl_get(self.server_capabilities, 'textDocumentSync', 'openClose') then
+ if not self.supports_method(ms.textDocument_didOpen) then
return
end
if not api.nvim_buf_is_loaded(bufnr) then
@@ -1053,4 +1061,45 @@ function Client:_on_exit(code, signal)
)
end
+--- @package
+--- Add a directory to the workspace folders.
+--- @param dir string?
+function Client:_add_workspace_folder(dir)
+ for _, folder in pairs(self.workspace_folders or {}) do
+ if folder.name == dir then
+ print(dir, 'is already part of this workspace')
+ return
+ end
+ end
+
+ local wf = assert(get_workspace_folders(nil, dir))
+
+ self:_notify(ms.workspace_didChangeWorkspaceFolders, {
+ event = { added = wf, removed = {} },
+ })
+
+ if not self.workspace_folders then
+ self.workspace_folders = {}
+ end
+ vim.list_extend(self.workspace_folders, wf)
+end
+
+--- @package
+--- Remove a directory to the workspace folders.
+--- @param dir string?
+function Client:_remove_workspace_folder(dir)
+ local wf = assert(get_workspace_folders(nil, dir))
+
+ self:_notify(ms.workspace_didChangeWorkspaceFolders, {
+ event = { added = {}, removed = wf },
+ })
+
+ for idx, folder in pairs(self.workspace_folders) do
+ if folder.name == dir then
+ table.remove(self.workspace_folders, idx)
+ break
+ end
+ end
+end
+
return Client
diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua
index 48c096c0c1..c85bb6aa32 100644
--- a/runtime/lua/vim/lsp/codelens.lua
+++ b/runtime/lua/vim/lsp/codelens.lua
@@ -79,7 +79,7 @@ function M.run()
local lenses_by_client = lens_cache_by_buf[bufnr] or {}
for client, lenses in pairs(lenses_by_client) do
for _, lens in pairs(lenses) do
- if lens.range.start.line == (line - 1) then
+ if lens.range.start.line == (line - 1) and lens.command and lens.command.command ~= '' then
table.insert(options, { client = client, lens = lens })
end
end
@@ -164,7 +164,7 @@ function M.display(lenses, bufnr, client_id)
return a.range.start.character < b.range.start.character
end)
for j, lens in ipairs(line_lenses) do
- local text = lens.command and lens.command.title or 'Unresolved lens ...'
+ local text = (lens.command and lens.command.title or 'Unresolved lens ...'):gsub('%s+', ' ')
table.insert(chunks, { text, 'LspCodeLens' })
if j < num_line_lenses then
table.insert(chunks, { ' | ', 'LspCodeLensSeparator' })
@@ -231,7 +231,7 @@ local function resolve_lenses(lenses, bufnr, client_id, callback)
countdown()
else
assert(client)
- client.request('codeLens/resolve', lens, function(_, result)
+ client.request(ms.codeLens_resolve, lens, function(_, result)
if api.nvim_buf_is_loaded(bufnr) and result and result.command then
lens.command = result.command
-- Eager display to have some sort of incremental feedback
@@ -299,14 +299,18 @@ function M.refresh(opts)
local bufnr = opts.bufnr and resolve_bufnr(opts.bufnr)
local buffers = bufnr and { bufnr }
or vim.tbl_filter(api.nvim_buf_is_loaded, api.nvim_list_bufs())
- local params = {
- textDocument = util.make_text_document_params(),
- }
for _, buf in ipairs(buffers) do
if not active_refreshes[buf] then
+ local params = {
+ textDocument = util.make_text_document_params(buf),
+ }
active_refreshes[buf] = true
- vim.lsp.buf_request(buf, ms.textDocument_codeLens, params, M.on_codelens)
+
+ local request_ids = vim.lsp.buf_request(buf, ms.textDocument_codeLens, params, M.on_codelens)
+ if vim.tbl_isempty(request_ids) then
+ active_refreshes[buf] = nil
+ end
end
end
end
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index daf4fec8d2..f9d394642c 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -3,6 +3,7 @@ local protocol = require('vim.lsp.protocol')
local ms = protocol.Methods
local util = require('vim.lsp.util')
local api = vim.api
+local completion = require('vim.lsp._completion')
--- @type table<string,lsp.Handler>
local M = {}
@@ -12,7 +13,7 @@ local M = {}
--- Writes to error buffer.
---@param ... string Will be concatenated before being written
local function err_message(...)
- vim.notify(table.concat(vim.tbl_flatten({ ... })), vim.log.levels.ERROR)
+ vim.notify(table.concat(vim.iter({ ... }):flatten():totable()), vim.log.levels.ERROR)
api.nvim_command('redraw')
end
@@ -22,16 +23,16 @@ M[ms.workspace_executeCommand] = function(_, _, _, _)
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
----@param result lsp.ProgressParams
+---@param params lsp.ProgressParams
---@param ctx lsp.HandlerContext
-M[ms.dollar_progress] = function(_, result, ctx)
+M[ms.dollar_progress] = function(_, params, ctx)
local client = vim.lsp.get_client_by_id(ctx.client_id)
if not client then
err_message('LSP[id=', tostring(ctx.client_id), '] client has shut down during progress update')
return vim.NIL
end
local kind = nil
- local value = result.value
+ local value = params.value
if type(value) == 'table' then
kind = value.kind
@@ -39,21 +40,21 @@ M[ms.dollar_progress] = function(_, result, ctx)
-- So that consumers always have it available, even if they consume a
-- subset of the full sequence
if kind == 'begin' then
- client.progress.pending[result.token] = value.title
+ client.progress.pending[params.token] = value.title
else
- value.title = client.progress.pending[result.token]
+ value.title = client.progress.pending[params.token]
if kind == 'end' then
- client.progress.pending[result.token] = nil
+ client.progress.pending[params.token] = nil
end
end
end
- client.progress:push(result)
+ client.progress:push(params)
api.nvim_exec_autocmds('LspProgress', {
pattern = kind,
modeline = false,
- data = { client_id = ctx.client_id, result = result },
+ data = { client_id = ctx.client_id, params = params },
})
end
@@ -253,26 +254,24 @@ M[ms.textDocument_references] = function(_, result, ctx, config)
local title = 'References'
local items = util.locations_to_items(result, client.offset_encoding)
+ local list = { title = title, items = items, context = ctx }
if config.loclist then
- vim.fn.setloclist(0, {}, ' ', { title = title, items = items, context = ctx })
- api.nvim_command('lopen')
+ vim.fn.setloclist(0, {}, ' ', list)
+ vim.cmd.lopen()
elseif config.on_list then
- assert(type(config.on_list) == 'function', 'on_list is not a function')
- config.on_list({ title = title, items = items, context = ctx })
+ assert(vim.is_callable(config.on_list), 'on_list is not a function')
+ config.on_list(list)
else
- vim.fn.setqflist({}, ' ', { title = title, items = items, context = ctx })
- api.nvim_command('botright copen')
+ vim.fn.setqflist({}, ' ', list)
+ vim.cmd('botright copen')
end
end
--- Return a function that converts LSP responses to list items and opens the list
---
---- The returned function has an optional {config} parameter that accepts a table
---- with the following keys:
----
---- loclist: (boolean) use the location list (default is to use the quickfix list)
+--- The returned function has an optional {config} parameter that accepts |vim.lsp.ListOpts|
---
----@param map_result function `((resp, bufnr) -> list)` to convert the response
+---@param map_result fun(resp, bufnr: integer): table to convert the response
---@param entity string name of the resource used in a `not found` error message
---@param title_fn fun(ctx: lsp.HandlerContext): string Function to call to generate list title
---@return lsp.Handler
@@ -286,15 +285,16 @@ local function response_to_list(map_result, entity, title_fn)
local title = title_fn(ctx)
local items = map_result(result, ctx.bufnr)
+ local list = { title = title, items = items, context = ctx }
if config.loclist then
- vim.fn.setloclist(0, {}, ' ', { title = title, items = items, context = ctx })
- api.nvim_command('lopen')
+ vim.fn.setloclist(0, {}, ' ', list)
+ vim.cmd.lopen()
elseif config.on_list then
- assert(type(config.on_list) == 'function', 'on_list is not a function')
- config.on_list({ title = title, items = items, context = ctx })
+ assert(vim.is_callable(config.on_list), 'on_list is not a function')
+ config.on_list(list)
else
- vim.fn.setqflist({}, ' ', { title = title, items = items, context = ctx })
- api.nvim_command('botright copen')
+ vim.fn.setqflist({}, ' ', list)
+ vim.cmd('botright copen')
end
end
end
@@ -354,7 +354,7 @@ M[ms.textDocument_completion] = function(_, result, _, _)
local textMatch = vim.fn.match(line_to_cursor, '\\k*$')
local prefix = line_to_cursor:sub(textMatch + 1)
- local matches = util.text_document_completion_list_to_complete_items(result, prefix)
+ local matches = completion._lsp_to_complete_items(result, prefix)
vim.fn.complete(textMatch + 1, matches)
end
@@ -428,7 +428,7 @@ local function location_handler(_, result, ctx, config)
-- textDocument/definition can return Location or Location[]
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
- if not vim.tbl_islist(result) then
+ if not vim.islist(result) then
result = { result }
end
@@ -436,7 +436,7 @@ local function location_handler(_, result, ctx, config)
local items = util.locations_to_items(result, client.offset_encoding)
if config.on_list then
- assert(type(config.on_list) == 'function', 'on_list is not a function')
+ assert(vim.is_callable(config.on_list), 'on_list is not a function')
config.on_list({ title = title, items = items })
return
end
@@ -444,8 +444,13 @@ local function location_handler(_, result, ctx, config)
util.jump_to_location(result[1], client.offset_encoding, config.reuse_win)
return
end
- vim.fn.setqflist({}, ' ', { title = title, items = items })
- api.nvim_command('botright copen')
+ if config.loclist then
+ vim.fn.setloclist(0, {}, ' ', { title = title, items = items })
+ vim.cmd.lopen()
+ else
+ vim.fn.setqflist({}, ' ', { title = title, items = items })
+ vim.cmd('botright copen')
+ end
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_declaration
@@ -555,7 +560,7 @@ local function make_call_hierarchy_handler(direction)
end
end
vim.fn.setqflist({}, ' ', { title = 'LSP call hierarchy', items = items })
- api.nvim_command('botright copen')
+ vim.cmd('botright copen')
end
end
@@ -565,6 +570,45 @@ M[ms.callHierarchy_incomingCalls] = make_call_hierarchy_handler('from')
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy_outgoingCalls
M[ms.callHierarchy_outgoingCalls] = make_call_hierarchy_handler('to')
+--- Displays type hierarchy in the quickfix window.
+local function make_type_hierarchy_handler()
+ --- @param result lsp.TypeHierarchyItem[]
+ return function(_, result, ctx, _)
+ if not result then
+ return
+ end
+ local function format_item(item)
+ if not item.detail or #item.detail == 0 then
+ return item.name
+ end
+ return string.format('%s %s', item.name, item.detail)
+ end
+ local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
+ local items = {}
+ for _, type_hierarchy_item in pairs(result) do
+ local col = util._get_line_byte_from_position(
+ ctx.bufnr,
+ type_hierarchy_item.range.start,
+ client.offset_encoding
+ )
+ table.insert(items, {
+ filename = assert(vim.uri_to_fname(type_hierarchy_item.uri)),
+ text = format_item(type_hierarchy_item),
+ lnum = type_hierarchy_item.range.start.line + 1,
+ col = col + 1,
+ })
+ end
+ vim.fn.setqflist({}, ' ', { title = 'LSP type hierarchy', items = items })
+ vim.cmd('botright copen')
+ end
+end
+
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#typeHierarchy_incomingCalls
+M[ms.typeHierarchy_subtypes] = make_type_hierarchy_handler()
+
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#typeHierarchy_outgoingCalls
+M[ms.typeHierarchy_supertypes] = make_type_hierarchy_handler()
+
--- @see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_logMessage
--- @param result lsp.LogMessageParams
M[ms.window_logMessage] = function(_, result, ctx, _)
@@ -615,7 +659,8 @@ M[ms.window_showDocument] = function(_, result, ctx, _)
if result.external then
-- TODO(lvimuser): ask the user for confirmation
- local ret, err = vim.ui.open(uri)
+ local cmd, err = vim.ui.open(uri)
+ local ret = cmd and cmd:wait(2000) or nil
if ret == nil or ret.code ~= 0 then
return {
diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua
index 797a1097f9..a79ae76eb9 100644
--- a/runtime/lua/vim/lsp/health.lua
+++ b/runtime/lua/vim/lsp/health.lua
@@ -51,6 +51,29 @@ end
local function check_watcher()
vim.health.start('vim.lsp: File watcher')
+
+ -- Only run the check if file watching has been enabled by a client.
+ local clients = vim.lsp.get_clients()
+ if
+ --- @param client vim.lsp.Client
+ vim.iter(clients):all(function(client)
+ local has_capability = vim.tbl_get(
+ client.capabilities,
+ 'workspace',
+ 'didChangeWatchedFiles',
+ 'dynamicRegistration'
+ )
+ local has_dynamic_capability =
+ client.dynamic_capabilities:get(vim.lsp.protocol.Methods.workspace_didChangeWatchedFiles)
+ return has_capability == nil
+ or has_dynamic_capability == nil
+ or client.workspace_folders == nil
+ end)
+ then
+ report_info('file watching "(workspace/didChangeWatchedFiles)" disabled on all clients')
+ return
+ end
+
local watchfunc = vim.lsp._watchfiles._watchfunc
assert(watchfunc)
local watchfunc_name --- @type string
diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua
index ec676ea97f..f98496456b 100644
--- a/runtime/lua/vim/lsp/inlay_hint.lua
+++ b/runtime/lua/vim/lsp/inlay_hint.lua
@@ -4,13 +4,30 @@ local ms = require('vim.lsp.protocol').Methods
local api = vim.api
local M = {}
----@class (private) vim.lsp.inlay_hint.bufstate
+---@class (private) vim.lsp.inlay_hint.globalstate Global state for inlay hints
+---@field enabled boolean Whether inlay hints are enabled for this scope
+---@type vim.lsp.inlay_hint.globalstate
+local globalstate = {
+ enabled = false,
+}
+
+---@class (private) vim.lsp.inlay_hint.bufstate: vim.lsp.inlay_hint.globalstate Buffer local state for inlay hints
---@field version? integer
---@field client_hints? table<integer, table<integer, lsp.InlayHint[]>> client_id -> (lnum -> hints)
---@field applied table<integer, integer> Last version of hints applied to this line
----@field enabled boolean Whether inlay hints are enabled for this buffer
---@type table<integer, vim.lsp.inlay_hint.bufstate>
-local bufstates = {}
+local bufstates = vim.defaulttable(function(_)
+ return setmetatable({ applied = {} }, {
+ __index = globalstate,
+ __newindex = function(state, key, value)
+ if globalstate[key] == value then
+ rawset(state, key, nil)
+ else
+ rawset(state, key, value)
+ end
+ end,
+ })
+end)
local namespace = api.nvim_create_namespace('vim_lsp_inlayhint')
local augroup = api.nvim_create_augroup('vim_lsp_inlayhint', {})
@@ -34,22 +51,22 @@ function M.on_inlayhint(err, result, ctx, _)
return
end
local bufstate = bufstates[bufnr]
- if not bufstate or not bufstate.enabled then
+ if not bufstate.enabled then
return
end
if not (bufstate.client_hints and bufstate.version) then
bufstate.client_hints = vim.defaulttable()
bufstate.version = ctx.version
end
- local hints_by_client = bufstate.client_hints
+ local client_hints = bufstate.client_hints
local client = assert(vim.lsp.get_client_by_id(client_id))
- local new_hints_by_lnum = vim.defaulttable()
+ local new_lnum_hints = vim.defaulttable()
local num_unprocessed = #result
if num_unprocessed == 0 then
- hints_by_client[client_id] = {}
+ client_hints[client_id] = {}
bufstate.version = ctx.version
- api.nvim__buf_redraw_range(bufnr, 0, -1)
+ api.nvim__redraw({ buf = bufnr, valid = true })
return
end
@@ -73,15 +90,15 @@ function M.on_inlayhint(err, result, ctx, _)
for _, hint in ipairs(result) do
local lnum = hint.position.line
hint.position.character = pos_to_byte(hint.position)
- table.insert(new_hints_by_lnum[lnum], hint)
+ table.insert(new_lnum_hints[lnum], hint)
end
- hints_by_client[client_id] = new_hints_by_lnum
+ client_hints[client_id] = new_lnum_hints
bufstate.version = ctx.version
- api.nvim__buf_redraw_range(bufnr, 0, -1)
+ api.nvim__redraw({ buf = bufnr, valid = true })
end
---- |lsp-handler| for the method `textDocument/inlayHint/refresh`
+--- |lsp-handler| for the method `workspace/inlayHint/refresh`
---@param ctx lsp.HandlerContext
---@private
function M.on_refresh(err, _, ctx, _)
@@ -91,11 +108,7 @@ function M.on_refresh(err, _, ctx, _)
for _, bufnr in ipairs(vim.lsp.get_buffers_by_client_id(ctx.client_id)) do
for _, winid in ipairs(api.nvim_list_wins()) do
if api.nvim_win_get_buf(winid) == bufnr then
- local bufstate = bufstates[bufnr]
- if bufstate then
- util._refresh(ms.textDocument_inlayHint, { bufnr = bufnr })
- break
- end
+ util._refresh(ms.textDocument_inlayHint, { bufnr = bufnr })
end
end
end
@@ -123,7 +136,8 @@ end
--- local hint = vim.lsp.inlay_hint.get({ bufnr = 0 })[1] -- 0 for current buffer
---
--- local client = vim.lsp.get_client_by_id(hint.client_id)
---- resolved_hint = client.request_sync('inlayHint/resolve', hint.inlay_hint, 100, 0).result
+--- local resp = client.request_sync('inlayHint/resolve', hint.inlay_hint, 100, 0)
+--- local resolved_hint = assert(resp and resp.result, resp.err)
--- vim.lsp.util.apply_text_edits(resolved_hint.textEdits, 0, client.encoding)
---
--- location = resolved_hint.label[1].location
@@ -154,7 +168,7 @@ function M.get(filter)
end
local bufstate = bufstates[bufnr]
- if not (bufstate and bufstate.client_hints) then
+ if not bufstate.client_hints then
return {}
end
@@ -175,19 +189,19 @@ function M.get(filter)
end
--- @type vim.lsp.inlay_hint.get.ret[]
- local hints = {}
+ local result = {}
for _, client in pairs(clients) do
- local hints_by_lnum = bufstate.client_hints[client.id]
- if hints_by_lnum then
+ local lnum_hints = bufstate.client_hints[client.id]
+ if lnum_hints then
for lnum = range.start.line, range['end'].line do
- local line_hints = hints_by_lnum[lnum] or {}
- for _, hint in pairs(line_hints) do
+ local hints = lnum_hints[lnum] or {}
+ for _, hint in pairs(hints) do
local line, char = hint.position.line, hint.position.character
if
(line > range.start.line or char >= range.start.character)
and (line < range['end'].line or char <= range['end'].character)
then
- table.insert(hints, {
+ table.insert(result, {
bufnr = bufnr,
client_id = client.id,
inlay_hint = hint,
@@ -197,18 +211,15 @@ function M.get(filter)
end
end
end
- return hints
+ return result
end
--- Clear inlay hints
---@param bufnr (integer) Buffer handle, or 0 for current
local function clear(bufnr)
- if bufnr == nil or bufnr == 0 then
+ if bufnr == 0 then
bufnr = api.nvim_get_current_buf()
end
- if not bufstates[bufnr] then
- return
- end
local bufstate = bufstates[bufnr]
local client_lens = (bufstate or {}).client_hints or {}
local client_ids = vim.tbl_keys(client_lens) --- @type integer[]
@@ -218,19 +229,18 @@ local function clear(bufnr)
end
end
api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1)
- api.nvim__buf_redraw_range(bufnr, 0, -1)
+ api.nvim__redraw({ buf = bufnr, valid = true })
end
--- Disable inlay hints for a buffer
----@param bufnr (integer|nil) Buffer handle, or 0 or nil for current
+---@param bufnr (integer) Buffer handle, or 0 for current
local function _disable(bufnr)
- if bufnr == nil or bufnr == 0 then
+ if bufnr == 0 then
bufnr = api.nvim_get_current_buf()
end
clear(bufnr)
- if bufstates[bufnr] then
- bufstates[bufnr] = { enabled = false, applied = {} }
- end
+ bufstates[bufnr] = nil
+ bufstates[bufnr].enabled = false
end
--- Refresh inlay hints, only if we have attached clients that support it
@@ -244,30 +254,38 @@ local function _refresh(bufnr, opts)
end
--- Enable inlay hints for a buffer
----@param bufnr (integer|nil) Buffer handle, or 0 or nil for current
+---@param bufnr (integer) Buffer handle, or 0 for current
local function _enable(bufnr)
- if bufnr == nil or bufnr == 0 then
+ if bufnr == 0 then
bufnr = api.nvim_get_current_buf()
end
- local bufstate = bufstates[bufnr]
- if not bufstate then
- bufstates[bufnr] = { applied = {}, enabled = true }
- api.nvim_create_autocmd('LspNotify', {
- buffer = bufnr,
- callback = function(opts)
- if
- opts.data.method ~= ms.textDocument_didChange
- and opts.data.method ~= ms.textDocument_didOpen
- then
- return
- end
- if bufstates[bufnr] and bufstates[bufnr].enabled then
- _refresh(bufnr, { client_id = opts.data.client_id })
- end
- end,
- group = augroup,
- })
- _refresh(bufnr)
+ bufstates[bufnr] = nil
+ bufstates[bufnr].enabled = true
+ _refresh(bufnr)
+end
+
+api.nvim_create_autocmd('LspNotify', {
+ callback = function(args)
+ ---@type integer
+ local bufnr = args.buf
+
+ if
+ args.data.method ~= ms.textDocument_didChange
+ and args.data.method ~= ms.textDocument_didOpen
+ then
+ return
+ end
+ if bufstates[bufnr].enabled then
+ _refresh(bufnr, { client_id = args.data.client_id })
+ end
+ end,
+ group = augroup,
+})
+api.nvim_create_autocmd('LspAttach', {
+ callback = function(args)
+ ---@type integer
+ local bufnr = args.buf
+
api.nvim_buf_attach(bufnr, false, {
on_reload = function(_, cb_bufnr)
clear(cb_bufnr)
@@ -278,32 +296,30 @@ local function _enable(bufnr)
end,
on_detach = function(_, cb_bufnr)
_disable(cb_bufnr)
+ bufstates[cb_bufnr] = nil
end,
})
- api.nvim_create_autocmd('LspDetach', {
- buffer = bufnr,
- callback = function(args)
- local clients = vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_inlayHint })
-
- if
- not vim.iter(clients):any(function(c)
- return c.id ~= args.data.client_id
- end)
- then
- _disable(bufnr)
- end
- end,
- group = augroup,
- })
- else
- bufstate.enabled = true
- _refresh(bufnr)
- end
-end
+ end,
+ group = augroup,
+})
+api.nvim_create_autocmd('LspDetach', {
+ callback = function(args)
+ ---@type integer
+ local bufnr = args.buf
+ local clients = vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_inlayHint })
+ if not vim.iter(clients):any(function(c)
+ return c.id ~= args.data.client_id
+ end) then
+ _disable(bufnr)
+ end
+ end,
+ group = augroup,
+})
api.nvim_set_decoration_provider(namespace, {
on_win = function(_, _, bufnr, topline, botline)
- local bufstate = bufstates[bufnr]
+ ---@type vim.lsp.inlay_hint.bufstate
+ local bufstate = rawget(bufstates, bufnr)
if not bufstate then
return
end
@@ -311,14 +327,18 @@ api.nvim_set_decoration_provider(namespace, {
if bufstate.version ~= util.buf_versions[bufnr] then
return
end
- local hints_by_client = assert(bufstate.client_hints)
+
+ if not bufstate.client_hints then
+ return
+ end
+ local client_hints = assert(bufstate.client_hints)
for lnum = topline, botline do
if bufstate.applied[lnum] ~= bufstate.version then
api.nvim_buf_clear_namespace(bufnr, namespace, lnum, lnum + 1)
- for _, hints_by_lnum in pairs(hints_by_client) do
- local line_hints = hints_by_lnum[lnum] or {}
- for _, hint in pairs(line_hints) do
+ for _, lnum_hints in pairs(client_hints) do
+ local hints = lnum_hints[lnum] or {}
+ for _, hint in pairs(hints) do
local text = ''
local label = hint.label
if type(label) == 'string' then
@@ -349,34 +369,65 @@ api.nvim_set_decoration_provider(namespace, {
end,
})
---- @param bufnr (integer|nil) Buffer handle, or 0 or nil for current
+--- Query whether inlay hint is enabled in the {filter}ed scope
+--- @param filter vim.lsp.inlay_hint.enable.Filter
--- @return boolean
--- @since 12
-function M.is_enabled(bufnr)
+function M.is_enabled(filter)
+ vim.validate({ filter = { filter, 'table', true } })
+ filter = filter or {}
+ local bufnr = filter.bufnr
+
vim.validate({ bufnr = { bufnr, 'number', true } })
- if bufnr == nil or bufnr == 0 then
+ if bufnr == nil then
+ return globalstate.enabled
+ elseif bufnr == 0 then
bufnr = api.nvim_get_current_buf()
end
- return bufstates[bufnr] and bufstates[bufnr].enabled or false
+ return bufstates[bufnr].enabled
end
---- Enables or disables inlay hints for a buffer.
+--- Optional filters |kwargs|, or `nil` for all.
+--- @class vim.lsp.inlay_hint.enable.Filter
+--- @inlinedoc
+--- Buffer number, or 0 for current buffer, or nil for all.
+--- @field bufnr integer?
+
+--- Enables or disables inlay hints for the {filter}ed scope.
---
--- To "toggle", pass the inverse of `is_enabled()`:
---
--- ```lua
---- vim.lsp.inlay_hint.enable(0, not vim.lsp.inlay_hint.is_enabled())
+--- vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled())
--- ```
---
---- @param bufnr (integer|nil) Buffer handle, or 0 or nil for current
--- @param enable (boolean|nil) true/nil to enable, false to disable
+--- @param filter vim.lsp.inlay_hint.enable.Filter?
--- @since 12
-function M.enable(bufnr, enable)
- vim.validate({ enable = { enable, 'boolean', true }, bufnr = { bufnr, 'number', true } })
- if enable == false then
- _disable(bufnr)
+function M.enable(enable, filter)
+ vim.validate({ enable = { enable, 'boolean', true }, filter = { filter, 'table', true } })
+ enable = enable == nil or enable
+ filter = filter or {}
+
+ if filter.bufnr == nil then
+ globalstate.enabled = enable
+ for _, bufnr in ipairs(api.nvim_list_bufs()) do
+ if api.nvim_buf_is_loaded(bufnr) then
+ if enable == false then
+ _disable(bufnr)
+ else
+ _enable(bufnr)
+ end
+ else
+ bufstates[bufnr] = nil
+ end
+ end
else
- _enable(bufnr)
+ if enable == false then
+ _disable(filter.bufnr)
+ else
+ _enable(filter.bufnr)
+ end
end
end
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index 599f02425e..419c2ff644 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -10,6 +10,8 @@ local function get_value_set(tbl)
return value_set
end
+local sysname = vim.uv.os_uname().sysname
+
-- Protocol for the Microsoft Language Server Protocol (mslsp)
local protocol = {}
@@ -835,7 +837,10 @@ function protocol.make_client_capabilities()
refreshSupport = true,
},
didChangeWatchedFiles = {
- dynamicRegistration = true,
+ -- TODO(lewis6991): do not advertise didChangeWatchedFiles on Linux
+ -- or BSD since all the current backends are too limited.
+ -- Ref: #27807, #28058, #23291, #26520
+ dynamicRegistration = sysname == 'Darwin' or sysname == 'Windows_NT',
relativePatternSupport = true,
},
inlayHint = {
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index 984e4f040a..3c63a12da2 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -621,95 +621,67 @@ local function merge_dispatchers(dispatchers)
return merged
end
---- Create a LSP RPC client factory that connects via TCP to the given host and port.
+--- Create a LSP RPC client factory that connects to either:
+---
+--- - a named pipe (windows)
+--- - a domain socket (unix)
+--- - a host and port via TCP
---
--- Return a function that can be passed to the `cmd` field for
--- |vim.lsp.start_client()| or |vim.lsp.start()|.
---
----@param host string host to connect to
----@param port integer port to connect to
+---@param host_or_path string host to connect to or path to a pipe/domain socket
+---@param port integer? TCP port to connect to. If absent the first argument must be a pipe
---@return fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient
-function M.connect(host, port)
+function M.connect(host_or_path, port)
return function(dispatchers)
dispatchers = merge_dispatchers(dispatchers)
- local tcp = assert(uv.new_tcp())
+ local handle = (
+ port == nil
+ and assert(
+ uv.new_pipe(false),
+ string.format('Pipe with name %s could not be opened.', host_or_path)
+ )
+ or assert(uv.new_tcp(), 'Could not create new TCP socket')
+ )
local closing = false
+ -- Connect returns a PublicClient synchronously so the caller
+ -- can immediately send messages before the connection is established
+ -- -> Need to buffer them until that happens
+ local connected = false
+ -- size should be enough because the client can't really do anything until initialization is done
+ -- which required a response from the server - implying the connection got established
+ local msgbuf = vim.ringbuf(10)
local transport = {
write = function(msg)
- tcp:write(msg)
- end,
- is_closing = function()
- return closing
- end,
- terminate = function()
- if not closing then
- closing = true
- tcp:shutdown()
- tcp:close()
- dispatchers.on_exit(0, 0)
+ if connected then
+ local _, err = handle:write(msg)
+ if err and not closing then
+ log.error('Error on handle:write: %q', err)
+ end
+ else
+ msgbuf:push(msg)
end
end,
- }
- local client = new_client(dispatchers, transport)
- tcp:connect(host, port, function(err)
- if err then
- vim.schedule(function()
- vim.notify(
- string.format('Could not connect to %s:%s, reason: %s', host, port, vim.inspect(err)),
- vim.log.levels.WARN
- )
- end)
- return
- end
- local handle_body = function(body)
- client:handle_body(body)
- end
- tcp:read_start(M.create_read_loop(handle_body, transport.terminate, function(read_err)
- client:on_error(M.client_errors.READ_ERROR, read_err)
- end))
- end)
-
- return public_client(client)
- end
-end
-
---- Create a LSP RPC client factory that connects via named pipes (Windows)
---- or unix domain sockets (Unix) to the given pipe_path (file path on
---- Unix and name on Windows).
----
---- Return a function that can be passed to the `cmd` field for
---- |vim.lsp.start_client()| or |vim.lsp.start()|.
----
----@param pipe_path string file path of the domain socket (Unix) or name of the named pipe (Windows) to connect to
----@return fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient
-function M.domain_socket_connect(pipe_path)
- return function(dispatchers)
- dispatchers = merge_dispatchers(dispatchers)
- local pipe =
- assert(uv.new_pipe(false), string.format('pipe with name %s could not be opened.', pipe_path))
- local closing = false
- local transport = {
- write = vim.schedule_wrap(function(msg)
- pipe:write(msg)
- end),
is_closing = function()
return closing
end,
terminate = function()
if not closing then
closing = true
- pipe:shutdown()
- pipe:close()
+ handle:shutdown()
+ handle:close()
dispatchers.on_exit(0, 0)
end
end,
}
local client = new_client(dispatchers, transport)
- pipe:connect(pipe_path, function(err)
+ local function on_connect(err)
if err then
+ local address = port == nil and host_or_path or (host_or_path .. ':' .. port)
vim.schedule(function()
vim.notify(
- string.format('Could not connect to :%s, reason: %s', pipe_path, vim.inspect(err)),
+ string.format('Could not connect to %s, reason: %s', address, vim.inspect(err)),
vim.log.levels.WARN
)
end)
@@ -718,10 +690,19 @@ function M.domain_socket_connect(pipe_path)
local handle_body = function(body)
client:handle_body(body)
end
- pipe:read_start(M.create_read_loop(handle_body, transport.terminate, function(read_err)
+ handle:read_start(M.create_read_loop(handle_body, transport.terminate, function(read_err)
client:on_error(M.client_errors.READ_ERROR, read_err)
end))
- end)
+ connected = true
+ for msg in msgbuf do
+ handle:write(msg)
+ end
+ end
+ if port == nil then
+ handle:connect(host_or_path, on_connect)
+ else
+ handle:connect(host_or_path, port, on_connect)
+ end
return public_client(client)
end
@@ -741,7 +722,7 @@ end
--- @param cmd string[] Command to start the LSP server.
--- @param dispatchers? vim.lsp.rpc.Dispatchers
--- @param extra_spawn_params? vim.lsp.rpc.ExtraSpawnParams
---- @return vim.lsp.rpc.PublicClient? : Client RPC object, with these methods:
+--- @return vim.lsp.rpc.PublicClient : Client RPC object, with these methods:
--- - `notify()` |vim.lsp.rpc.notify()|
--- - `request()` |vim.lsp.rpc.request()|
--- - `is_closing()` returns a boolean indicating if the RPC is closing.
@@ -816,8 +797,7 @@ function M.start(cmd, dispatchers, extra_spawn_params)
end
local msg =
string.format('Spawning language server with cmd: `%s` failed%s', vim.inspect(cmd), sfx)
- vim.notify(msg, vim.log.levels.WARN)
- return nil
+ error(msg)
end
sysobj = sysobj_or_err --[[@as vim.SystemObj]]
diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua
index 20ac0a125f..ef2502b12e 100644
--- a/runtime/lua/vim/lsp/semantic_tokens.lua
+++ b/runtime/lua/vim/lsp/semantic_tokens.lua
@@ -394,7 +394,7 @@ function STHighlighter:process_response(response, client, version)
current_result.namespace_cleared = false
-- redraw all windows displaying buffer
- api.nvim__buf_redraw_range(self.bufnr, 0, -1)
+ api.nvim__redraw({ buf = self.bufnr, valid = true })
end
--- on_win handler for the decoration provider (see |nvim_set_decoration_provider|)
@@ -570,9 +570,9 @@ local M = {}
--- client.server_capabilities.semanticTokensProvider = nil
--- ```
---
----@param bufnr integer
----@param client_id integer
----@param opts? table Optional keyword arguments
+---@param bufnr (integer) Buffer number, or `0` for current buffer
+---@param client_id (integer) The ID of the |vim.lsp.Client|
+---@param opts? (table) Optional keyword arguments
--- - debounce (integer, default: 200): Debounce token requests
--- to the server by the given number in milliseconds
function M.start(bufnr, client_id, opts)
@@ -581,6 +581,10 @@ function M.start(bufnr, client_id, opts)
client_id = { client_id, 'n', false },
})
+ if bufnr == 0 then
+ bufnr = api.nvim_get_current_buf()
+ end
+
opts = opts or {}
assert(
(not opts.debounce or type(opts.debounce) == 'number'),
@@ -626,14 +630,18 @@ end
--- of `start()`, so you should only need this function to manually disengage the semantic
--- token engine without fully detaching the LSP client from the buffer.
---
----@param bufnr integer
----@param client_id integer
+---@param bufnr (integer) Buffer number, or `0` for current buffer
+---@param client_id (integer) The ID of the |vim.lsp.Client|
function M.stop(bufnr, client_id)
vim.validate({
bufnr = { bufnr, 'n', false },
client_id = { client_id, 'n', false },
})
+ if bufnr == 0 then
+ bufnr = api.nvim_get_current_buf()
+ end
+
local highlighter = STHighlighter.active[bufnr]
if not highlighter then
return
@@ -741,12 +749,15 @@ end
--- mark will be deleted by the semantic token engine when appropriate; for
--- example, when the LSP sends updated tokens. This function is intended for
--- use inside |LspTokenUpdate| callbacks.
----@param token (table) a semantic token, found as `args.data.token` in |LspTokenUpdate|.
----@param bufnr (integer) the buffer to highlight
+---@param token (table) A semantic token, found as `args.data.token` in |LspTokenUpdate|
+---@param bufnr (integer) The buffer to highlight, or `0` for current buffer
---@param client_id (integer) The ID of the |vim.lsp.Client|
---@param hl_group (string) Highlight group name
---@param opts? vim.lsp.semantic_tokens.highlight_token.Opts Optional parameters:
function M.highlight_token(token, bufnr, client_id, hl_group, opts)
+ if bufnr == 0 then
+ bufnr = api.nvim_get_current_buf()
+ end
local highlighter = STHighlighter.active[bufnr]
if not highlighter then
return
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index f8e5b6a90d..5a229a1169 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -1,5 +1,4 @@
local protocol = require('vim.lsp.protocol')
-local snippet = require('vim.lsp._snippet_grammar')
local validate = vim.validate
local api = vim.api
local list_extend = vim.list_extend
@@ -343,68 +342,6 @@ local function get_line_byte_from_position(bufnr, position, offset_encoding)
return col
end
---- Process and return progress reports from lsp server
----@private
----@deprecated Use vim.lsp.status() or access client.progress directly
-function M.get_progress_messages()
- vim.deprecate('vim.lsp.util.get_progress_messages()', 'vim.lsp.status()', '0.11')
- local new_messages = {}
- local progress_remove = {}
-
- for _, client in ipairs(vim.lsp.get_clients()) do
- local groups = {}
- for progress in client.progress do
- local value = progress.value
- if type(value) == 'table' and value.kind then
- local group = groups[progress.token]
- if not group then
- group = {
- done = false,
- progress = true,
- title = 'empty title',
- }
- groups[progress.token] = group
- end
- group.title = value.title or group.title
- group.cancellable = value.cancellable or group.cancellable
- if value.kind == 'end' then
- group.done = true
- end
- group.message = value.message or group.message
- group.percentage = value.percentage or group.percentage
- end
- end
-
- for _, group in pairs(groups) do
- table.insert(new_messages, group)
- end
-
- local messages = client.messages
- local data = messages
- for token, ctx in pairs(data.progress) do
- local new_report = {
- name = data.name,
- title = ctx.title or 'empty title',
- message = ctx.message,
- percentage = ctx.percentage,
- done = ctx.done,
- progress = true,
- }
- table.insert(new_messages, new_report)
-
- if ctx.done then
- table.insert(progress_remove, { client = client, token = token })
- end
- end
- end
-
- for _, item in ipairs(progress_remove) do
- item.client.messages.progress[item.token] = nil
- end
-
- return new_messages
-end
-
--- Applies a list of text edits to a buffer.
---@param text_edits table list of `TextEdit` objects
---@param bufnr integer Buffer id
@@ -541,38 +478,6 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
end
end
--- local valid_windows_path_characters = "[^<>:\"/\\|?*]"
--- local valid_unix_path_characters = "[^/]"
--- https://github.com/davidm/lua-glob-pattern
--- https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names
--- function M.glob_to_regex(glob)
--- end
-
---- Can be used to extract the completion items from a
---- `textDocument/completion` request, which may return one of
---- `CompletionItem[]`, `CompletionList` or null.
----
---- Note that this method doesn't apply `itemDefaults` to `CompletionList`s, and hence the returned
---- results might be incorrect.
----
----@deprecated
----@param result table The result of a `textDocument/completion` request
----@return lsp.CompletionItem[] List of completion items
----@see https://microsoft.github.io/language-server-protocol/specification#textDocument_completion
-function M.extract_completion_items(result)
- vim.deprecate('vim.lsp.util.extract_completion_items()', nil, '0.11')
- if type(result) == 'table' and result.items then
- -- result is a `CompletionList`
- return result.items
- elseif result ~= nil then
- -- result is `CompletionItem[]`
- return result
- else
- -- result is `null`
- return {}
- end
-end
-
--- Applies a `TextDocumentEdit`, which is a list of changes to a single
--- document.
---
@@ -615,38 +520,6 @@ function M.apply_text_document_edit(text_document_edit, index, offset_encoding)
M.apply_text_edits(text_document_edit.edits, bufnr, offset_encoding)
end
---- Parses snippets in a completion entry.
----
----@deprecated
----@param input string unparsed snippet
----@return string parsed snippet
-function M.parse_snippet(input)
- vim.deprecate('vim.lsp.util.parse_snippet()', nil, '0.11')
- local ok, parsed = pcall(function()
- return snippet.parse(input)
- end)
- if not ok then
- return input
- end
-
- return tostring(parsed)
-end
-
---- Turns the result of a `textDocument/completion` request into vim-compatible
---- |complete-items|.
----
----@deprecated
----@param result table The result of a `textDocument/completion` call, e.g.
---- from |vim.lsp.buf.completion()|, which may be one of `CompletionItem[]`,
---- `CompletionList` or `null`
----@param prefix (string) the prefix to filter the completion items
----@return table[] items
----@see complete-items
-function M.text_document_completion_list_to_complete_items(result, prefix)
- vim.deprecate('vim.lsp.util.text_document_completion_list_to_complete_items()', nil, '0.11')
- return vim.lsp._completion._lsp_to_complete_items(result, prefix)
-end
-
local function path_components(path)
return vim.split(path, '/', { plain = true })
end
@@ -690,7 +563,7 @@ end
---
--- It deletes existing buffers that conflict with the renamed file name only when
--- * `opts` requests overwriting; or
---- * the conflicting buffers are not loaded, so that deleting thme does not result in data loss.
+--- * the conflicting buffers are not loaded, so that deleting them does not result in data loss.
---
--- @param old_fname string
--- @param new_fname string