summaryrefslogtreecommitdiff
path: root/vim/pack/downloads/opt/lsp/autoload
diff options
context:
space:
mode:
Diffstat (limited to 'vim/pack/downloads/opt/lsp/autoload')
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/buffer.vim194
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/callhierarchy.vim194
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/capabilities.vim501
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/codeaction.vim135
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/codelens.vim32
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/completion.vim708
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/diag.vim920
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/handlers.vim305
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/hover.vim137
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/inlayhints.vim235
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/lsp.vim1304
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/lspserver.vim1982
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/markdown.vim757
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/offset.vim168
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/options.vim179
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/outline.vim304
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/selection.vim107
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/semantichighlight.vim264
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/signature.vim145
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/snippet.vim82
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/symbol.vim1014
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/textedit.vim321
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/typehierarchy.vim174
-rw-r--r--vim/pack/downloads/opt/lsp/autoload/lsp/util.vim364
24 files changed, 10526 insertions, 0 deletions
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/buffer.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/buffer.vim
new file mode 100644
index 0000000..5a07654
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/buffer.vim
@@ -0,0 +1,194 @@
+vim9script
+
+# Functions for managing the per-buffer LSP server information
+
+import './util.vim'
+
+# A buffer can have one or more attached language servers. The
+# "bufnrToServers" Dict contains the list of language servers attached to a
+# buffer. The buffer number is the key for the "bufnrToServers" Dict. The
+# value is the List of attached language servers.
+var bufnrToServers: dict<list<dict<any>>> = {}
+
+# Add "lspserver" to "bufnrToServers" map for buffer "bnr".
+export def BufLspServerSet(bnr: number, lspserver: dict<any>)
+ if !bufnrToServers->has_key(bnr)
+ bufnrToServers[bnr] = []
+ endif
+
+ bufnrToServers[bnr]->add(lspserver)
+enddef
+
+# Remove "lspserver" from "bufnrToServers" map for buffer "bnr".
+export def BufLspServerRemove(bnr: number, lspserver: dict<any>)
+ if !bufnrToServers->has_key(bnr)
+ return
+ endif
+
+ var servers: list<dict<any>> = bufnrToServers[bnr]
+ servers = servers->filter((key, srv) => srv.id != lspserver.id)
+
+ if servers->empty()
+ bufnrToServers->remove(bnr)
+ else
+ bufnrToServers[bnr] = servers
+ endif
+enddef
+
+var SupportedCheckFns = {
+ callHierarchy: (lspserver) => lspserver.isCallHierarchyProvider,
+ codeAction: (lspserver) => lspserver.isCodeActionProvider,
+ codeLens: (lspserver) => lspserver.isCodeLensProvider,
+ completion: (lspserver) => lspserver.isCompletionProvider,
+ declaration: (lspserver) => lspserver.isDeclarationProvider,
+ definition: (lspserver) => lspserver.isDefinitionProvider,
+ documentFormatting: (lspserver) => lspserver.isDocumentFormattingProvider,
+ documentHighlight: (lspserver) => lspserver.isDocumentHighlightProvider,
+ documentSymbol: (lspserver) => lspserver.isDocumentSymbolProvider,
+ foldingRange: (lspserver) => lspserver.isFoldingRangeProvider,
+ hover: (lspserver) => lspserver.isHoverProvider,
+ implementation: (lspserver) => lspserver.isImplementationProvider,
+ inlayHint: (lspserver) => lspserver.isInlayHintProvider ||
+ lspserver.isClangdInlayHintsProvider,
+ references: (lspserver) => lspserver.isReferencesProvider,
+ rename: (lspserver) => lspserver.isRenameProvider,
+ selectionRange: (lspserver) => lspserver.isSelectionRangeProvider,
+ semanticTokens: (lspserver) => lspserver.isSemanticTokensProvider,
+ signatureHelp: (lspserver) => lspserver.isSignatureHelpProvider,
+ typeDefinition: (lspserver) => lspserver.isTypeDefinitionProvider,
+ typeHierarchy: (lspserver) => lspserver.isTypeHierarchyProvider,
+ workspaceSymbol: (lspserver) => lspserver.isWorkspaceSymbolProvider
+}
+
+# Returns the LSP server for the buffer "bnr". If "feature" is specified,
+# then returns the LSP server that provides the "feature".
+# Returns an empty dict if the server is not found.
+export def BufLspServerGet(bnr: number, feature: string = null_string): dict<any>
+ if !bufnrToServers->has_key(bnr)
+ return {}
+ endif
+
+ if bufnrToServers[bnr]->empty()
+ return {}
+ endif
+
+ if feature == null_string
+ return bufnrToServers[bnr][0]
+ endif
+
+ if !SupportedCheckFns->has_key(feature)
+ # If this happns it is a programming error, and should be fixed in the
+ # source code
+ :throw $'Error: ''{feature}'' is not a valid feature'
+ endif
+
+ var SupportedCheckFn = SupportedCheckFns[feature]
+
+ var possibleLSPs: list<dict<any>> = []
+
+ for lspserver in bufnrToServers[bnr]
+ if !lspserver.ready || !SupportedCheckFn(lspserver)
+ continue
+ endif
+
+ possibleLSPs->add(lspserver)
+ endfor
+
+ if possibleLSPs->empty()
+ return {}
+ endif
+
+ # LSP server is configured to be a provider for "feature"
+ for lspserver in possibleLSPs
+ var has_feature: bool = lspserver.features->get(feature, false)
+ if has_feature
+ return lspserver
+ endif
+ endfor
+
+ # Return the first LSP server that supports "feature" and doesn't have it
+ # disabled
+ for lspserver in possibleLSPs
+ if lspserver.featureEnabled(feature)
+ return lspserver
+ endif
+ endfor
+
+ return {}
+enddef
+
+# Returns the LSP server for the buffer "bnr" and with ID "id". Returns an empty
+# dict if the server is not found.
+export def BufLspServerGetById(bnr: number, id: number): dict<any>
+ if !bufnrToServers->has_key(bnr)
+ return {}
+ endif
+
+ for lspserver in bufnrToServers[bnr]
+ if lspserver.id == id
+ return lspserver
+ endif
+ endfor
+
+ return {}
+enddef
+
+# Returns the LSP servers for the buffer "bnr". Returns an empty list if the
+# servers are not found.
+export def BufLspServersGet(bnr: number): list<dict<any>>
+ if !bufnrToServers->has_key(bnr)
+ return []
+ endif
+
+ return bufnrToServers[bnr]
+enddef
+
+# Returns the LSP server for the current buffer with the optionally "feature".
+# Returns an empty dict if the server is not found.
+export def CurbufGetServer(feature: string = null_string): dict<any>
+ return BufLspServerGet(bufnr(), feature)
+enddef
+
+# Returns the LSP servers for the current buffer. Returns an empty list if the
+# servers are not found.
+export def CurbufGetServers(): list<dict<any>>
+ return BufLspServersGet(bufnr())
+enddef
+
+export def BufHasLspServer(bnr: number): bool
+ var lspserver = BufLspServerGet(bnr)
+
+ return !lspserver->empty()
+enddef
+
+# Returns the LSP server for the current buffer with the optinally "feature" if
+# it is running and is ready.
+# Returns an empty dict if the server is not found or is not ready.
+export def CurbufGetServerChecked(feature: string = null_string): dict<any>
+ var fname: string = @%
+ if fname->empty() || &filetype->empty()
+ return {}
+ endif
+
+ var lspserver: dict<any> = CurbufGetServer(feature)
+ if lspserver->empty()
+ if feature == null_string
+ util.ErrMsg($'Language server for "{&filetype}" file type is not found')
+ else
+ util.ErrMsg($'Language server for "{&filetype}" file type supporting "{feature}" feature is not found')
+ endif
+ return {}
+ endif
+ if !lspserver.running
+ util.ErrMsg($'Language server for "{&filetype}" file type is not running')
+ return {}
+ endif
+ if !lspserver.ready
+ util.ErrMsg($'Language server for "{&filetype}" file type is not ready')
+ return {}
+ endif
+
+ return lspserver
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/callhierarchy.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/callhierarchy.vim
new file mode 100644
index 0000000..aca6a55
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/callhierarchy.vim
@@ -0,0 +1,194 @@
+vim9script
+
+# Functions for dealing with call hierarchy (incoming/outgoing calls)
+
+import './util.vim'
+import './buffer.vim' as buf
+
+# Jump to the location of the symbol under the cursor in the call hierarchy
+# tree window.
+def CallHierarchyItemJump()
+ var item: dict<any> = w:LspCallHierItemMap[line('.')].item
+ util.JumpToLspLocation(item, '')
+enddef
+
+# Refresh the call hierarchy tree for the symbol at index "idx".
+def CallHierarchyTreeItemRefresh(idx: number)
+ var treeItem: dict<any> = w:LspCallHierItemMap[idx]
+
+ if treeItem.open
+ # Already retrieved the children for this item
+ return
+ endif
+
+ if !treeItem->has_key('children')
+ # First time retrieving the children for the item at index "idx"
+ var lspserver = buf.BufLspServerGet(w:LspBufnr, 'callHierarchy')
+ if lspserver->empty() || !lspserver.running
+ return
+ endif
+
+ var reply: any
+ if w:LspCallHierIncoming
+ reply = lspserver.getIncomingCalls(treeItem.item)
+ else
+ reply = lspserver.getOutgoingCalls(treeItem.item)
+ endif
+
+ treeItem.children = []
+ if !reply->empty()
+ for item in reply
+ treeItem.children->add({item: w:LspCallHierIncoming ? item.from :
+ item.to, open: false})
+ endfor
+ endif
+ endif
+
+ # Clear and redisplay the tree in the window
+ treeItem.open = true
+ var save_cursor = getcurpos()
+ CallHierarchyTreeRefresh()
+ setpos('.', save_cursor)
+enddef
+
+# Open the call hierarchy tree item under the cursor
+def CallHierarchyTreeItemOpen()
+ CallHierarchyTreeItemRefresh(line('.'))
+enddef
+
+# Refresh the entire call hierarchy tree
+def CallHierarchyTreeRefreshCmd()
+ w:LspCallHierItemMap[2].open = false
+ w:LspCallHierItemMap[2]->remove('children')
+ CallHierarchyTreeItemRefresh(2)
+enddef
+
+# Display the incoming call hierarchy tree
+def CallHierarchyTreeIncomingCmd()
+ w:LspCallHierItemMap[2].open = false
+ w:LspCallHierItemMap[2]->remove('children')
+ w:LspCallHierIncoming = true
+ CallHierarchyTreeItemRefresh(2)
+enddef
+
+# Display the outgoing call hierarchy tree
+def CallHierarchyTreeOutgoingCmd()
+ w:LspCallHierItemMap[2].open = false
+ w:LspCallHierItemMap[2]->remove('children')
+ w:LspCallHierIncoming = false
+ CallHierarchyTreeItemRefresh(2)
+enddef
+
+# Close the call hierarchy tree item under the cursor
+def CallHierarchyTreeItemClose()
+ var treeItem: dict<any> = w:LspCallHierItemMap[line('.')]
+ treeItem.open = false
+ var save_cursor = getcurpos()
+ CallHierarchyTreeRefresh()
+ setpos('.', save_cursor)
+enddef
+
+# Recursively add the call hierarchy items to w:LspCallHierItemMap
+def CallHierarchyTreeItemShow(incoming: bool, treeItem: dict<any>, pfx: string)
+ var item = treeItem.item
+ var treePfx: string
+ if treeItem.open && treeItem->has_key('children')
+ treePfx = has('gui_running') ? '▼' : '-'
+ else
+ treePfx = has('gui_running') ? '▶' : '+'
+ endif
+ var fname = util.LspUriToFile(item.uri)
+ var s = $'{pfx}{treePfx} {item.name} ({fname->fnamemodify(":t")} [{fname->fnamemodify(":h")}])'
+ append('$', s)
+ w:LspCallHierItemMap->add(treeItem)
+ if treeItem.open && treeItem->has_key('children')
+ for child in treeItem.children
+ CallHierarchyTreeItemShow(incoming, child, $'{pfx} ')
+ endfor
+ endif
+enddef
+
+def CallHierarchyTreeRefresh()
+ :setlocal modifiable
+ :silent! :%d _
+
+ setline(1, $'# {w:LspCallHierIncoming ? "Incoming calls to" : "Outgoing calls from"} "{w:LspCallHierarchyTree.item.name}"')
+ w:LspCallHierItemMap = [{}, {}]
+ CallHierarchyTreeItemShow(w:LspCallHierIncoming, w:LspCallHierarchyTree, '')
+ :setlocal nomodifiable
+enddef
+
+def CallHierarchyTreeShow(incoming: bool, prepareItem: dict<any>,
+ items: list<dict<any>>)
+ var save_bufnr = bufnr()
+ var wid = bufwinid('LSP-CallHierarchy')
+ if wid != -1
+ wid->win_gotoid()
+ else
+ :new LSP-CallHierarchy
+ :setlocal buftype=nofile
+ :setlocal bufhidden=wipe
+ :setlocal noswapfile
+ :setlocal nonumber nornu
+ :setlocal fdc=0 signcolumn=no
+
+ :nnoremap <buffer> <CR> <ScriptCmd>CallHierarchyItemJump()<CR>
+ :nnoremap <buffer> - <ScriptCmd>CallHierarchyTreeItemOpen()<CR>
+ :nnoremap <buffer> + <ScriptCmd>CallHierarchyTreeItemClose()<CR>
+ :command -buffer LspCallHierarchyRefresh CallHierarchyTreeRefreshCmd()
+ :command -buffer LspCallHierarchyIncoming CallHierarchyTreeIncomingCmd()
+ :command -buffer LspCallHierarchyOutgoing CallHierarchyTreeOutgoingCmd()
+
+ :syntax match Comment '^#.*$'
+ :syntax match Directory '(.*)$'
+ endif
+
+ w:LspBufnr = save_bufnr
+ w:LspCallHierIncoming = incoming
+ w:LspCallHierarchyTree = {}
+ w:LspCallHierarchyTree.item = prepareItem
+ w:LspCallHierarchyTree.open = true
+ w:LspCallHierarchyTree.children = []
+ for item in items
+ w:LspCallHierarchyTree.children->add({item: incoming ? item.from : item.to, open: false})
+ endfor
+
+ CallHierarchyTreeRefresh()
+
+ :setlocal nomodified
+ :setlocal nomodifiable
+enddef
+
+export def IncomingCalls(lspserver: dict<any>)
+ var prepareReply = lspserver.prepareCallHierarchy()
+ if prepareReply->empty()
+ util.WarnMsg('No incoming calls')
+ return
+ endif
+
+ var reply = lspserver.getIncomingCalls(prepareReply)
+ if reply->empty()
+ util.WarnMsg('No incoming calls')
+ return
+ endif
+
+ CallHierarchyTreeShow(true, prepareReply, reply)
+enddef
+
+export def OutgoingCalls(lspserver: dict<any>)
+ var prepareReply = lspserver.prepareCallHierarchy()
+ if prepareReply->empty()
+ util.WarnMsg('No outgoing calls')
+ return
+ endif
+
+ var reply = lspserver.getOutgoingCalls(prepareReply)
+ if reply->empty()
+ util.WarnMsg('No outgoing calls')
+ return
+ endif
+
+ CallHierarchyTreeShow(false, prepareReply, reply)
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/capabilities.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/capabilities.vim
new file mode 100644
index 0000000..46f5973
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/capabilities.vim
@@ -0,0 +1,501 @@
+vim9script
+
+# Functions for managing the LSP server and client capabilities
+
+import './options.vim' as opt
+
+# Process the server capabilities
+# interface ServerCapabilities
+export def ProcessServerCaps(lspserver: dict<any>, caps: dict<any>)
+ var serverEncoding = 'utf-16'
+ if lspserver.caps->has_key('positionEncoding')
+ serverEncoding = lspserver.caps.positionEncoding
+ elseif lspserver.caps->has_key('~additionalInitResult_offsetEncoding')
+ serverEncoding = lspserver.caps['~additionalInitResult_offsetEncoding']
+ endif
+
+ if lspserver.forceOffsetEncoding != ''
+ serverEncoding = lspserver.forceOffsetEncoding
+ endif
+
+ # one of 'utf-8', 'utf-16' or 'utf-32'
+ if serverEncoding == 'utf-8'
+ lspserver.posEncoding = 8
+ elseif serverEncoding == 'utf-16'
+ lspserver.posEncoding = 16
+ else
+ lspserver.posEncoding = 32
+ endif
+
+ if has('patch-9.0.1629') && lspserver.posEncoding != 32
+ lspserver.needOffsetEncoding = true
+ else
+ lspserver.needOffsetEncoding = false
+ endif
+
+ # completionProvider
+ if lspserver.caps->has_key('completionProvider')
+ lspserver.isCompletionProvider = true
+ if lspserver.caps.completionProvider->has_key('resolveProvider')
+ lspserver.isCompletionResolveProvider =
+ lspserver.caps.completionProvider.resolveProvider
+ else
+ lspserver.isCompletionResolveProvider = false
+ endif
+ else
+ lspserver.isCompletionProvider = false
+ lspserver.isCompletionResolveProvider = false
+ endif
+
+ # definitionProvider
+ if lspserver.caps->has_key('definitionProvider')
+ if lspserver.caps.definitionProvider->type() == v:t_bool
+ lspserver.isDefinitionProvider = lspserver.caps.definitionProvider
+ else
+ lspserver.isDefinitionProvider = true
+ endif
+ else
+ lspserver.isDefinitionProvider = false
+ endif
+
+ # declarationProvider
+ if lspserver.caps->has_key('declarationProvider')
+ if lspserver.caps.declarationProvider->type() == v:t_bool
+ lspserver.isDeclarationProvider = lspserver.caps.declarationProvider
+ else
+ lspserver.isDeclarationProvider = true
+ endif
+ else
+ lspserver.isDeclarationProvider = false
+ endif
+
+ # typeDefinitionProvider
+ if lspserver.caps->has_key('typeDefinitionProvider')
+ if lspserver.caps.typeDefinitionProvider->type() == v:t_bool
+ lspserver.isTypeDefinitionProvider = lspserver.caps.typeDefinitionProvider
+ else
+ lspserver.isTypeDefinitionProvider = true
+ endif
+ else
+ lspserver.isTypeDefinitionProvider = false
+ endif
+
+ # implementationProvider
+ if lspserver.caps->has_key('implementationProvider')
+ if lspserver.caps.implementationProvider->type() == v:t_bool
+ lspserver.isImplementationProvider = lspserver.caps.implementationProvider
+ else
+ lspserver.isImplementationProvider = true
+ endif
+ else
+ lspserver.isImplementationProvider = false
+ endif
+
+ # signatureHelpProvider
+ if lspserver.caps->has_key('signatureHelpProvider')
+ lspserver.isSignatureHelpProvider = true
+ else
+ lspserver.isSignatureHelpProvider = false
+ endif
+
+ # hoverProvider
+ if lspserver.caps->has_key('hoverProvider')
+ if lspserver.caps.hoverProvider->type() == v:t_bool
+ lspserver.isHoverProvider = lspserver.caps.hoverProvider
+ else
+ lspserver.isHoverProvider = true
+ endif
+ else
+ lspserver.isHoverProvider = false
+ endif
+
+ # referencesProvider
+ if lspserver.caps->has_key('referencesProvider')
+ if lspserver.caps.referencesProvider->type() == v:t_bool
+ lspserver.isReferencesProvider = lspserver.caps.referencesProvider
+ else
+ lspserver.isReferencesProvider = true
+ endif
+ else
+ lspserver.isReferencesProvider = false
+ endif
+
+ # documentHighlightProvider
+ if lspserver.caps->has_key('documentHighlightProvider')
+ if lspserver.caps.documentHighlightProvider->type() == v:t_bool
+ lspserver.isDocumentHighlightProvider =
+ lspserver.caps.documentHighlightProvider
+ else
+ lspserver.isDocumentHighlightProvider = true
+ endif
+ else
+ lspserver.isDocumentHighlightProvider = false
+ endif
+
+ # documentSymbolProvider
+ if lspserver.caps->has_key('documentSymbolProvider')
+ if lspserver.caps.documentSymbolProvider->type() == v:t_bool
+ lspserver.isDocumentSymbolProvider =
+ lspserver.caps.documentSymbolProvider
+ else
+ lspserver.isDocumentSymbolProvider = true
+ endif
+ else
+ lspserver.isDocumentSymbolProvider = false
+ endif
+
+ # documentFormattingProvider
+ if lspserver.caps->has_key('documentFormattingProvider')
+ if lspserver.caps.documentFormattingProvider->type() == v:t_bool
+ lspserver.isDocumentFormattingProvider =
+ lspserver.caps.documentFormattingProvider
+ else
+ lspserver.isDocumentFormattingProvider = true
+ endif
+ else
+ lspserver.isDocumentFormattingProvider = false
+ endif
+
+ # callHierarchyProvider
+ if lspserver.caps->has_key('callHierarchyProvider')
+ if lspserver.caps.callHierarchyProvider->type() == v:t_bool
+ lspserver.isCallHierarchyProvider =
+ lspserver.caps.callHierarchyProvider
+ else
+ lspserver.isCallHierarchyProvider = true
+ endif
+ else
+ lspserver.isCallHierarchyProvider = false
+ endif
+
+ # semanticTokensProvider
+ if lspserver.caps->has_key('semanticTokensProvider')
+ lspserver.isSemanticTokensProvider = true
+ lspserver.semanticTokensLegend =
+ lspserver.caps.semanticTokensProvider.legend
+ lspserver.semanticTokensRange =
+ lspserver.caps.semanticTokensProvider->get('range', false)
+ if lspserver.caps.semanticTokensProvider->has_key('full')
+ if lspserver.caps.semanticTokensProvider.full->type() == v:t_bool
+ lspserver.semanticTokensFull =
+ lspserver.caps.semanticTokensProvider.full
+ lspserver.semanticTokensDelta = false
+ else
+ lspserver.semanticTokensFull = true
+ if lspserver.caps.semanticTokensProvider.full->has_key('delta')
+ lspserver.semanticTokensDelta =
+ lspserver.caps.semanticTokensProvider.full.delta
+ else
+ lspserver.semanticTokensDelta = false
+ endif
+ endif
+ else
+ lspserver.semanticTokensFull = false
+ lspserver.semanticTokensDelta = false
+ endif
+ else
+ lspserver.isSemanticTokensProvider = false
+ endif
+
+ # typeHierarchyProvider
+ if lspserver.caps->has_key('typeHierarchyProvider')
+ lspserver.isTypeHierarchyProvider = true
+ else
+ lspserver.isTypeHierarchyProvider = false
+ endif
+
+ # renameProvider
+ if lspserver.caps->has_key('renameProvider')
+ if lspserver.caps.renameProvider->type() == v:t_bool
+ lspserver.isRenameProvider = lspserver.caps.renameProvider
+ else
+ lspserver.isRenameProvider = true
+ endif
+ else
+ lspserver.isRenameProvider = false
+ endif
+
+ # codeActionProvider
+ if lspserver.caps->has_key('codeActionProvider')
+ if lspserver.caps.codeActionProvider->type() == v:t_bool
+ lspserver.isCodeActionProvider = lspserver.caps.codeActionProvider
+ else
+ lspserver.isCodeActionProvider = true
+ endif
+ else
+ lspserver.isCodeActionProvider = false
+ endif
+
+ # codeLensProvider
+ if lspserver.caps->has_key('codeLensProvider')
+ lspserver.isCodeLensProvider = true
+ if lspserver.caps.codeLensProvider->has_key('resolveProvider')
+ lspserver.isCodeLensResolveProvider = true
+ else
+ lspserver.isCodeLensResolveProvider = false
+ endif
+ else
+ lspserver.isCodeLensProvider = false
+ endif
+
+ # workspaceSymbolProvider
+ if lspserver.caps->has_key('workspaceSymbolProvider')
+ if lspserver.caps.workspaceSymbolProvider->type() == v:t_bool
+ lspserver.isWorkspaceSymbolProvider =
+ lspserver.caps.workspaceSymbolProvider
+ else
+ lspserver.isWorkspaceSymbolProvider = true
+ endif
+ else
+ lspserver.isWorkspaceSymbolProvider = false
+ endif
+
+ # selectionRangeProvider
+ if lspserver.caps->has_key('selectionRangeProvider')
+ if lspserver.caps.selectionRangeProvider->type() == v:t_bool
+ lspserver.isSelectionRangeProvider =
+ lspserver.caps.selectionRangeProvider
+ else
+ lspserver.isSelectionRangeProvider = true
+ endif
+ else
+ lspserver.isSelectionRangeProvider = false
+ endif
+
+ # foldingRangeProvider
+ if lspserver.caps->has_key('foldingRangeProvider')
+ if lspserver.caps.foldingRangeProvider->type() == v:t_bool
+ lspserver.isFoldingRangeProvider = lspserver.caps.foldingRangeProvider
+ else
+ lspserver.isFoldingRangeProvider = true
+ endif
+ else
+ lspserver.isFoldingRangeProvider = false
+ endif
+
+ # inlayHintProvider
+ if lspserver.caps->has_key('inlayHintProvider')
+ if lspserver.caps.inlayHintProvider->type() == v:t_bool
+ lspserver.isInlayHintProvider = lspserver.caps.inlayHintProvider
+ else
+ lspserver.isInlayHintProvider = true
+ endif
+ else
+ lspserver.isInlayHintProvider = false
+ endif
+
+ # clangdInlayHintsProvider
+ if lspserver.caps->has_key('clangdInlayHintsProvider')
+ lspserver.isClangdInlayHintsProvider =
+ lspserver.caps.clangdInlayHintsProvider
+ else
+ lspserver.isClangdInlayHintsProvider = false
+ endif
+
+ # textDocumentSync capabilities
+ lspserver.supportsDidSave = false
+ # Default to TextDocumentSyncKind.None
+ lspserver.textDocumentSync = 0
+ if lspserver.caps->has_key('textDocumentSync')
+ if lspserver.caps.textDocumentSync->type() == v:t_bool
+ || lspserver.caps.textDocumentSync->type() == v:t_number
+ lspserver.supportsDidSave = lspserver.caps.textDocumentSync
+ lspserver.textDocumentSync = lspserver.caps.textDocumentSync
+ elseif lspserver.caps.textDocumentSync->type() == v:t_dict
+ # "save"
+ if lspserver.caps.textDocumentSync->has_key('save')
+ if lspserver.caps.textDocumentSync.save->type() == v:t_bool
+ || lspserver.caps.textDocumentSync.save->type() == v:t_number
+ lspserver.supportsDidSave = lspserver.caps.textDocumentSync.save
+ elseif lspserver.caps.textDocumentSync.save->type() == v:t_dict
+ lspserver.supportsDidSave = true
+ endif
+ endif
+ # "change"
+ if lspserver.caps.textDocumentSync->has_key('change')
+ lspserver.textDocumentSync = lspserver.caps.textDocumentSync.change
+ endif
+ endif
+ endif
+enddef
+
+# Return all the LSP client capabilities
+export def GetClientCaps(): dict<any>
+ # client capabilities (ClientCapabilities)
+ var clientCaps: dict<any> = {
+ general: {
+ # Currently we always send character count as position offset,
+ # which meanas only utf-32 is supported.
+ # Adding utf-16 simply for good mesure, as I'm scared some servers will
+ # give up if they don't support utf-32 only.
+ positionEncodings: ['utf-32', 'utf-16']
+ },
+ textDocument: {
+ callHierarchy: {
+ dynamicRegistration: false
+ },
+ codeAction: {
+ dynamicRegistration: false,
+ codeActionLiteralSupport: {
+ codeActionKind: {
+ valueSet: ['', 'quickfix', 'refactor', 'refactor.extract',
+ 'refactor.inline', 'refactor.rewrite', 'source',
+ 'source.organizeImports']
+ }
+ },
+ isPreferredSupport: true,
+ disabledSupport: true
+ },
+ codeLens: {
+ dynamicRegistration: false
+ },
+ completion: {
+ dynamicRegistration: false,
+ completionItem: {
+ documentationFormat: ['markdown', 'plaintext'],
+ resolveSupport: {
+ properties: ['detail', 'documentation']
+ },
+ snippetSupport: opt.lspOptions.snippetSupport,
+ insertReplaceSupport: false
+ },
+ completionItemKind: {
+ valueSet: range(1, 25)
+ }
+ },
+ declaration: {
+ dynamicRegistration: false,
+ linkSupport: true
+ },
+ definition: {
+ dynamicRegistration: false,
+ linkSupport: true
+ },
+ documentHighlight: {
+ dynamicRegistration: false
+ },
+ documentSymbol: {
+ dynamicRegistration: false,
+ symbolKind: {
+ valueSet: range(1, 25)
+ },
+ hierarchicalDocumentSymbolSupport: true,
+ labelSupport: false
+ },
+ foldingRange: {
+ dynamicRegistration: false,
+ rangeLimit: 5000,
+ lineFoldingOnly: true,
+ foldingRangeKind: {
+ valueSet: ['comment', 'imports', 'region']
+ },
+ foldingRange: {
+ collapsedText: true
+ }
+ },
+ formatting: {
+ dynamicRegistration: false
+ },
+ hover: {
+ dynamicRegistration: false,
+ contentFormat: ['markdown', 'plaintext']
+ },
+ implementation: {
+ dynamicRegistration: false,
+ linkSupport: true
+ },
+ inlayHint: {
+ dynamicRegistration: false
+ },
+ publishDiagnostics: {
+ relatedInformation: false,
+ versionSupport: true,
+ codeDescriptionSupport: true,
+ dataSupport: true
+ },
+ rangeFormatting: {
+ dynamicRegistration: false
+ },
+ references: {
+ dynamicRegistration: false
+ },
+ rename: {
+ dynamicRegistration: false,
+ prepareSupport: false,
+ },
+ selectionRange: {
+ dynamicRegistration: false,
+ },
+ signatureHelp: {
+ dynamicRegistration: false,
+ signatureInformation: {
+ documentationFormat: ['markdown', 'plaintext'],
+ activeParameterSupport: true
+ }
+ },
+ semanticTokens: {
+ dynamicRegistration: false,
+ requests: {
+ range: false,
+ full: {
+ delta: true
+ }
+ },
+ tokenTypes: [
+ 'type', 'class', 'enum', 'interface', 'struct', 'typeParameter',
+ 'parameter', 'variable', 'property', 'enumMember', 'event',
+ 'function', 'method', 'macro', 'keyword', 'modifier', 'comment',
+ 'string', 'number', 'regexp', 'operator'
+ ],
+ tokenModifiers: [
+ 'declaration', 'definition', 'readonly', 'static', 'deprecated',
+ 'abstract', 'async', 'modification', 'documentation',
+ 'defaultLibrary'
+ ],
+ formats: ['relative'],
+ overlappingTokenSupport: false,
+ multilineTokenSupport: false,
+ serverCancelSupport: false,
+ augmentsSyntaxTokens: true
+ },
+ synchronization: {
+ dynamicRegistration: false,
+ didSave: true,
+ willSave: false,
+ WillSaveWaitUntil: false
+ },
+ typeDefinition: {
+ dynamicRegistration: false,
+ linkSupport: true
+ },
+ typeHierarchy: {
+ dynamicRegistration: false,
+ }
+ },
+ window: {},
+ workspace: {
+ workspaceFolders: true,
+ applyEdit: true,
+ workspaceEdit: {
+ resourceOperations: ['rename', 'create', 'delete']
+ },
+ configuration: true,
+ symbol: {
+ dynamicRegistration: false
+ }
+ },
+ # This is the way clangd expects to be informated about supported encodings:
+ # https://clangd.llvm.org/extensions#utf-8-offsets
+ offsetEncoding: ['utf-32', 'utf-16']
+ }
+
+ # Vim patch 1629 is needed to properly encode/decode UTF-16 offsets
+ if has('patch-9.0.1629')
+ clientCaps.general.positionEncodings = ['utf-32', 'utf-16', 'utf-8']
+ clientCaps.offsetEncoding = ['utf-32', 'utf-16', 'utf-8']
+ endif
+
+ return clientCaps
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/codeaction.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/codeaction.vim
new file mode 100644
index 0000000..fb2a91a
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/codeaction.vim
@@ -0,0 +1,135 @@
+vim9script
+
+# Functions related to handling LSP code actions to fix diagnostics.
+
+import './util.vim'
+import './textedit.vim'
+import './options.vim' as opt
+
+var CommandHandlers: dict<func>
+
+export def RegisterCmdHandler(cmd: string, Handler: func)
+ CommandHandlers[cmd] = Handler
+enddef
+
+export def DoCommand(lspserver: dict<any>, cmd: dict<any>)
+ if cmd->has_key('command') && CommandHandlers->has_key(cmd.command)
+ var CmdHandler: func = CommandHandlers[cmd.command]
+ try
+ call CmdHandler(cmd)
+ catch
+ util.ErrMsg($'"{cmd.command}" handler raised exception {v:exception}')
+ endtry
+ else
+ lspserver.executeCommand(cmd)
+ endif
+enddef
+
+# Apply the code action selected by the user.
+export def HandleCodeAction(lspserver: dict<any>, selAction: dict<any>)
+ # textDocument/codeAction can return either Command[] or CodeAction[].
+ # If it is a CodeAction, it can have either an edit, a command or both.
+ # Edits should be executed first.
+ # Both Command and CodeAction interfaces has "command" member
+ # so we should check "command" type - for Command it will be "string"
+ if selAction->has_key('edit')
+ || (selAction->has_key('command') && selAction.command->type() == v:t_dict)
+ # selAction is a CodeAction instance, apply edit and command
+ if selAction->has_key('edit')
+ # apply edit first
+ textedit.ApplyWorkspaceEdit(selAction.edit)
+ endif
+ if selAction->has_key('command')
+ DoCommand(lspserver, selAction.command)
+ endif
+ else
+ # selAction is a Command instance, apply it directly
+ DoCommand(lspserver, selAction)
+ endif
+enddef
+
+# Process the list of code actions returned by the LSP server, ask the user to
+# choose one action from the list and then apply it.
+# If "query" is a number, then apply the corresponding action in the list.
+# If "query" is a regular expression starting with "/", then apply the action
+# matching the search string in the list.
+# If "query" is a regular string, then apply the action matching the string.
+# If "query" is an empty string, then if the "usePopupInCodeAction" option is
+# configured by the user, then display the list of items in a popup menu.
+# Otherwise display the items in an input list and prompt the user to select
+# an action.
+export def ApplyCodeAction(lspserver: dict<any>, actionlist: list<dict<any>>, query: string): void
+ var actions = actionlist
+
+ if opt.lspOptions.hideDisabledCodeActions
+ actions = actions->filter((ix, act) => !act->has_key('disabled'))
+ endif
+
+ if actions->empty()
+ # no action can be performed
+ util.WarnMsg('No code action is available')
+ return
+ endif
+
+ var text: list<string> = []
+ var act: dict<any>
+ for i in actions->len()->range()
+ act = actions[i]
+ var t: string = act.title->substitute('\r\n', '\\r\\n', 'g')
+ t = t->substitute('\n', '\\n', 'g')
+ text->add(printf(" %d. %s ", i + 1, t))
+ endfor
+
+ var choice: number
+
+ var query_ = query->trim()
+ if query_ =~ '^\d\+$' # digit
+ choice = query_->str2nr()
+ elseif query_ =~ '^/' # regex
+ choice = 1 + util.Indexof(actions, (i, a) => a.title =~ query_[1 : ])
+ elseif query_ != '' # literal string
+ choice = 1 + util.Indexof(actions, (i, a) => a.title[0 : query_->len() - 1] == query_)
+ elseif opt.lspOptions.usePopupInCodeAction
+ # Use a popup menu to show the code action
+ popup_create(text, {
+ pos: 'botleft',
+ line: 'cursor-1',
+ col: 'cursor',
+ zindex: 1000,
+ cursorline: 1,
+ mapping: 0,
+ wrap: 0,
+ title: 'Code action',
+ callback: (_, result) => {
+ # Invalid item selected or closed the popup
+ if result <= 0 || result > text->len()
+ return
+ endif
+
+ # Do the code action
+ HandleCodeAction(lspserver, actions[result - 1])
+ },
+ filter: (winid, key) => {
+ if key == 'h' || key == 'l'
+ winid->popup_close(-1)
+ elseif key->str2nr() > 0
+ # assume less than 10 entries are present
+ winid->popup_close(key->str2nr())
+ else
+ return popup_filter_menu(winid, key)
+ endif
+ return 1
+ },
+ })
+ else
+ choice = inputlist(['Code action:'] + text)
+ endif
+
+ if choice < 1 || choice > text->len()
+ return
+ endif
+
+ HandleCodeAction(lspserver, actions[choice - 1])
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/codelens.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/codelens.vim
new file mode 100644
index 0000000..082edf7
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/codelens.vim
@@ -0,0 +1,32 @@
+vim9script
+
+import './codeaction.vim'
+
+# Functions related to handling LSP code lens
+
+export def ProcessCodeLens(lspserver: dict<any>, bnr: number, codeLensItems: list<dict<any>>)
+ var text: list<string> = []
+ for i in codeLensItems->len()->range()
+ var item = codeLensItems[i]
+ if !item->has_key('command')
+ # resolve the code lens
+ item = lspserver.resolveCodeLens(bnr, item)
+ if item->empty()
+ continue
+ endif
+ codeLensItems[i] = item
+ endif
+ text->add(printf("%d. %s\t| L%s:%s", i + 1, item.command.title,
+ item.range.start.line + 1,
+ getline(item.range.start.line + 1)))
+ endfor
+
+ var choice = inputlist(['Code Lens:'] + text)
+ if choice < 1 || choice > codeLensItems->len()
+ return
+ endif
+
+ codeaction.DoCommand(lspserver, codeLensItems[choice - 1].command)
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/completion.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/completion.vim
new file mode 100644
index 0000000..8ce5899
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/completion.vim
@@ -0,0 +1,708 @@
+vim9script
+
+# LSP completion related functions
+
+import './util.vim'
+import './buffer.vim' as buf
+import './options.vim' as opt
+import './textedit.vim'
+import './snippet.vim'
+import './codeaction.vim'
+
+# per-filetype omni-completion enabled/disabled table
+var ftypeOmniCtrlMap: dict<bool> = {}
+
+var defaultKinds: dict<string> = {
+ 'Text': 't',
+ 'Method': 'm',
+ 'Function': 'f',
+ 'Constructor': 'C',
+ 'Field': 'F',
+ 'Variable': 'v',
+ 'Class': 'c',
+ 'Interface': 'i',
+ 'Module': 'M',
+ 'Property': 'p',
+ 'Unit': 'u',
+ 'Value': 'V',
+ 'Enum': 'e',
+ 'Keyword': 'k',
+ 'Snippet': 'S',
+ 'Color': 'C',
+ 'File': 'f',
+ 'Reference': 'r',
+ 'Folder': 'F',
+ 'EnumMember': 'E',
+ 'Constant': 'd',
+ 'Struct': 's',
+ 'Event': 'E',
+ 'Operator': 'o',
+ 'TypeParameter': 'T',
+ 'Buffer': 'B',
+}
+
+# Returns true if omni-completion is enabled for filetype "ftype".
+# Otherwise, returns false.
+def LspOmniComplEnabled(ftype: string): bool
+ return ftypeOmniCtrlMap->get(ftype, false)
+enddef
+
+# Enables or disables omni-completion for filetype "fype"
+export def OmniComplSet(ftype: string, enabled: bool)
+ ftypeOmniCtrlMap->extend({[ftype]: enabled})
+enddef
+
+# Map LSP complete item kind to a character
+def LspCompleteItemKindChar(kind: number): string
+ var kindMap: list<string> = [
+ '',
+ 'Text',
+ 'Method',
+ 'Function',
+ 'Constructor',
+ 'Field',
+ 'Variable',
+ 'Class',
+ 'Interface',
+ 'Module',
+ 'Property',
+ 'Unit',
+ 'Value',
+ 'Enum',
+ 'Keyword',
+ 'Snippet',
+ 'Color',
+ 'File',
+ 'Reference',
+ 'Folder',
+ 'EnumMember',
+ 'Constant',
+ 'Struct',
+ 'Event',
+ 'Operator',
+ 'TypeParameter',
+ 'Buffer'
+ ]
+
+ if kind > 26
+ return ''
+ endif
+
+ var kindName = kindMap[kind]
+ var kindValue = defaultKinds[kindName]
+
+ var lspOpts = opt.lspOptions
+ if lspOpts.customCompletionKinds &&
+ lspOpts.completionKinds->has_key(kindName)
+ kindValue = lspOpts.completionKinds[kindName]
+ endif
+
+ return kindValue
+enddef
+
+# Remove all the snippet placeholders from "str" and return the value.
+# Based on a similar function in the vim-lsp plugin.
+def MakeValidWord(str_arg: string): string
+ var str = str_arg->substitute('\$[0-9]\+\|\${\%(\\.\|[^}]\)\+}', '', 'g')
+ str = str->substitute('\\\(.\)', '\1', 'g')
+ var valid = str->matchstr('^[^"'' (<{\[\t\r\n]\+')
+ if valid->empty()
+ return str
+ endif
+ if valid =~ ':$'
+ return valid[: -2]
+ endif
+ return valid
+enddef
+
+# add completion from current buf
+def CompletionFromBuffer(items: list<dict<any>>)
+ var words = {}
+ var start = reltime()
+ var timeout = opt.lspOptions.bufferCompletionTimeout
+ var linenr = 1
+ for line in getline(1, '$')
+ for word in line->split('\W\+')
+ if !words->has_key(word) && word->len() > 1
+ words[word] = 1
+ items->add({
+ label: word,
+ data: {
+ entryNames: [word],
+ },
+ kind: 26,
+ documentation: "",
+ })
+ endif
+ endfor
+ # Check every 200 lines if timeout is exceeded
+ if timeout > 0 && linenr % 200 == 0 &&
+ start->reltime()->reltimefloat() * 1000 > timeout
+ break
+ endif
+ linenr += 1
+ endfor
+enddef
+
+# process the 'textDocument/completion' reply from the LSP server
+# Result: CompletionItem[] | CompletionList | null
+export def CompletionReply(lspserver: dict<any>, cItems: any)
+ lspserver.completeItemsIsIncomplete = false
+ if cItems->empty()
+ if lspserver.omniCompletePending
+ lspserver.completeItems = []
+ lspserver.omniCompletePending = false
+ endif
+ return
+ endif
+
+ var items: list<dict<any>>
+ if cItems->type() == v:t_list
+ items = cItems
+ else
+ items = cItems.items
+ lspserver.completeItemsIsIncomplete = cItems->get('isIncomplete', false)
+ endif
+
+ var lspOpts = opt.lspOptions
+
+ # Get the keyword prefix before the current cursor column.
+ var chcol = charcol('.')
+ var starttext = chcol == 1 ? '' : getline('.')[ : chcol - 2]
+ var [prefix, start_idx, end_idx] = starttext->matchstrpos('\k*$')
+ if lspOpts.completionMatcherValue == opt.COMPLETIONMATCHER_ICASE
+ prefix = prefix->tolower()
+ endif
+
+ var start_col = start_idx + 1
+
+ if lspOpts.ultisnipsSupport
+ snippet.CompletionUltiSnips(prefix, items)
+ elseif lspOpts.vsnipSupport
+ snippet.CompletionVsnip(items)
+ endif
+
+ if lspOpts.useBufferCompletion
+ CompletionFromBuffer(items)
+ endif
+
+ var completeItems: list<dict<any>> = []
+ var itemsUsed: list<string> = []
+ for item in items
+ var d: dict<any> = {}
+
+ # TODO: Add proper support for item.textEdit.newText and
+ # item.textEdit.range. Keep in mind that item.textEdit.range can start
+ # way before the typed keyword.
+ if item->has_key('textEdit') &&
+ lspOpts.completionMatcherValue != opt.COMPLETIONMATCHER_FUZZY
+ var start_charcol: number
+ if !prefix->empty()
+ start_charcol = charidx(starttext, start_idx) + 1
+ else
+ start_charcol = chcol
+ endif
+ var textEdit = item.textEdit
+ var textEditRange: dict<any> = {}
+ if textEdit->has_key('range')
+ textEditRange = textEdit.range
+ elseif textEdit->has_key('insert')
+ textEditRange = textEdit.insert
+ endif
+ var textEditStartCol =
+ util.GetCharIdxWithoutCompChar(bufnr(), textEditRange.start)
+ if textEditStartCol != start_charcol
+ var offset = start_charcol - textEditStartCol - 1
+ d.word = textEdit.newText[offset : ]
+ else
+ d.word = textEdit.newText
+ endif
+ elseif item->has_key('insertText')
+ d.word = item.insertText
+ else
+ d.word = item.label
+ endif
+
+ if item->get('insertTextFormat', 1) == 2
+ # snippet completion. Needs a snippet plugin to expand the snippet.
+ # Remove all the snippet placeholders
+ d.word = MakeValidWord(d.word)
+ elseif !lspserver.completeItemsIsIncomplete || lspOpts.useBufferCompletion
+ # Filter items only when "isIncomplete" is set (otherwise server would
+ # have done the filtering) or when buffer completion is enabled
+
+ # plain text completion
+ if !prefix->empty()
+ # If the completion item text doesn't start with the current (case
+ # ignored) keyword prefix, skip it.
+ var filterText: string = item->get('filterText', d.word)
+ if lspOpts.completionMatcherValue == opt.COMPLETIONMATCHER_ICASE
+ if filterText->tolower()->stridx(prefix) != 0
+ continue
+ endif
+ # If the completion item text doesn't fuzzy match with the current
+ # keyword prefix, skip it.
+ elseif lspOpts.completionMatcherValue == opt.COMPLETIONMATCHER_FUZZY
+ if matchfuzzy([filterText], prefix)->empty()
+ continue
+ endif
+ # If the completion item text doesn't start with the current keyword
+ # prefix, skip it.
+ else
+ if filterText->stridx(prefix) != 0
+ continue
+ endif
+ endif
+ endif
+ endif
+
+ d.abbr = item.label
+ d.dup = 1
+
+ if lspOpts.completionMatcherValue == opt.COMPLETIONMATCHER_ICASE
+ d.icase = 1
+ endif
+
+ if item->has_key('kind') && item.kind != null
+ # namespace CompletionItemKind
+ # map LSP kind to complete-item-kind
+ d.kind = LspCompleteItemKindChar(item.kind)
+ endif
+
+ if lspserver.completionLazyDoc
+ d.info = 'Lazy doc'
+ else
+ if item->has_key('detail') && !item.detail->empty()
+ # Solve a issue where if a server send a detail field
+ # with a "\n", on the menu will be everything joined with
+ # a "^@" separating it. (example: clangd)
+ d.menu = item.detail->split("\n")[0]
+ endif
+ if item->has_key('documentation')
+ var itemDoc = item.documentation
+ if itemDoc->type() == v:t_string && !itemDoc->empty()
+ d.info = itemDoc
+ elseif itemDoc->type() == v:t_dict
+ && itemDoc.value->type() == v:t_string
+ d.info = itemDoc.value
+ endif
+ endif
+ endif
+
+ # Score is used for sorting.
+ d.score = item->get('sortText')
+ if d.score->empty()
+ d.score = item->get('label', '')
+ endif
+
+ # Dont include duplicate items
+ if lspOpts.filterCompletionDuplicates
+ var key = d->get('word', '') ..
+ d->get('info', '') ..
+ d->get('kind', '') ..
+ d->get('score', '') ..
+ d->get('abbr', '') ..
+ d->get('dup', '')
+ if index(itemsUsed, key) != -1
+ continue
+ endif
+ add(itemsUsed, key)
+ endif
+
+ d.user_data = item
+ completeItems->add(d)
+ endfor
+
+ if lspOpts.completionMatcherValue != opt.COMPLETIONMATCHER_FUZZY
+ # Lexographical sort (case-insensitive).
+ completeItems->sort((a, b) =>
+ a.score == b.score ? 0 : a.score >? b.score ? 1 : -1)
+ endif
+
+ if lspOpts.autoComplete && !lspserver.omniCompletePending
+ if completeItems->empty()
+ # no matches
+ return
+ endif
+
+ var m = mode()
+ if m != 'i' && m != 'R' && m != 'Rv'
+ # If not in insert or replace mode, then don't start the completion
+ return
+ endif
+
+ if completeItems->len() == 1
+ && getline('.')->matchstr($'\C{completeItems[0].word}\>') != ''
+ # only one complete match. No need to show the completion popup
+ return
+ endif
+
+ completeItems->complete(start_col)
+ else
+ lspserver.completeItems = completeItems
+ lspserver.omniCompletePending = false
+ endif
+enddef
+
+# Check if completion item is selected
+def CheckCompletionItemSel(label: string): bool
+ var cInfo = complete_info()
+ if cInfo->empty() || !cInfo.pum_visible || cInfo.selected == -1
+ return false
+ endif
+ var selItem = cInfo.items->get(cInfo.selected, {})
+ if selItem->empty()
+ || selItem->type() != v:t_dict
+ || selItem.user_data->type() != v:t_dict
+ || selItem.user_data.label != label
+ return false
+ endif
+ return true
+enddef
+
+# Process the completion documentation
+def ShowCompletionDocumentation(cItem: any)
+ if cItem->empty() || cItem->type() != v:t_dict
+ return
+ endif
+
+ # check if completion item is still selected
+ if !CheckCompletionItemSel(cItem.label)
+ return
+ endif
+
+ var infoText: list<string>
+ var infoKind: string
+
+ if cItem->has_key('detail') && !cItem.detail->empty()
+ # Solve a issue where if a server send the detail field with "\n",
+ # on the completion popup, everything will be joined with "^@"
+ # (example: typescript-language-server)
+ infoText->extend(cItem.detail->split("\n"))
+ endif
+
+ if cItem->has_key('documentation')
+ if !infoText->empty()
+ infoText->extend(['- - -'])
+ endif
+ var cItemDoc = cItem.documentation
+ if cItemDoc->type() == v:t_dict
+ # MarkupContent
+ if cItemDoc.kind == 'plaintext'
+ infoText->extend(cItemDoc.value->split("\n"))
+ infoKind = 'text'
+ elseif cItemDoc.kind == 'markdown'
+ infoText->extend(cItemDoc.value->split("\n"))
+ infoKind = 'lspgfm'
+ else
+ util.ErrMsg($'Unsupported documentation type ({cItemDoc.kind})')
+ return
+ endif
+ elseif cItemDoc->type() == v:t_string
+ infoText->extend(cItemDoc->split("\n"))
+ else
+ util.ErrMsg($'Unsupported documentation ({cItemDoc->string()})')
+ return
+ endif
+ endif
+
+ if infoText->empty()
+ return
+ endif
+
+ # check if completion item is changed in meantime
+ if !CheckCompletionItemSel(cItem.label)
+ return
+ endif
+
+ # autoComplete or &omnifunc with &completeopt =~ 'popup'
+ var id = popup_findinfo()
+ if id > 0
+ var bufnr = id->winbufnr()
+ id->popup_settext(infoText)
+ infoKind->setbufvar(bufnr, '&ft')
+ id->popup_show()
+ else
+ # &omnifunc with &completeopt =~ 'preview'
+ try
+ :wincmd P
+ :setlocal modifiable
+ bufnr()->deletebufline(1, '$')
+ infoText->append(0)
+ [1, 1]->cursor()
+ exe $'setlocal ft={infoKind}'
+ :wincmd p
+ catch /E441/ # No preview window
+ endtry
+ endif
+enddef
+
+# process the 'completionItem/resolve' reply from the LSP server
+# Result: CompletionItem
+export def CompletionResolveReply(lspserver: dict<any>, cItem: any)
+ ShowCompletionDocumentation(cItem)
+enddef
+
+# Return trigger kind and trigger char. If completion trigger is not a keyword
+# and not one of the triggerCharacters, return -1 for triggerKind.
+def GetTriggerAttributes(lspserver: dict<any>): list<any>
+ var triggerKind: number = 1
+ var triggerChar: string = ''
+
+ # Trigger kind is 1 for keyword and 2 for trigger char initiated completion.
+ var line: string = getline('.')
+ var cur_col = charcol('.')
+ if line[cur_col - 2] !~ '\k'
+ var trigChars = lspserver.completionTriggerChars
+ var trigidx = trigChars->index(line[cur_col - 2])
+ if trigidx == -1
+ triggerKind = -1
+ else
+ triggerKind = 2
+ triggerChar = trigChars[trigidx]
+ endif
+ endif
+ return [triggerKind, triggerChar]
+enddef
+
+
+# omni complete handler
+def g:LspOmniFunc(findstart: number, base: string): any
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('completion')
+ if lspserver->empty()
+ return -2
+ endif
+
+ if findstart
+
+ var [triggerKind, triggerChar] = GetTriggerAttributes(lspserver)
+ if triggerKind < 0
+ # previous character is not a keyword character or a trigger character,
+ # so cancel omni completion.
+ return -2
+ endif
+
+ # first send all the changes in the current buffer to the LSP server
+ listener_flush()
+
+ lspserver.omniCompletePending = true
+ lspserver.completeItems = []
+
+ # initiate a request to LSP server to get list of completions
+ lspserver.getCompletion(triggerKind, triggerChar)
+
+ # locate the start of the word
+ var line = getline('.')->strpart(0, col('.') - 1)
+ var keyword = line->matchstr('\k\+$')
+ lspserver.omniCompleteKeyword = keyword
+ return line->len() - keyword->len()
+ else
+ # Wait for the list of matches from the LSP server
+ var count: number = 0
+ while lspserver.omniCompletePending && count < 1000
+ if complete_check()
+ return v:none
+ endif
+ sleep 2m
+ count += 1
+ endwhile
+
+ if lspserver.omniCompletePending
+ return v:none
+ endif
+
+ var res: list<dict<any>> = lspserver.completeItems
+ var prefix = lspserver.omniCompleteKeyword
+
+ # Don't attempt to filter on the items, when "isIncomplete" is set
+ if prefix->empty() || lspserver.completeItemsIsIncomplete
+ return res
+ endif
+
+ var lspOpts = opt.lspOptions
+ if lspOpts.completionMatcherValue == opt.COMPLETIONMATCHER_FUZZY
+ return res->matchfuzzy(prefix, { key: 'word' })
+ endif
+
+ if lspOpts.completionMatcherValue == opt.COMPLETIONMATCHER_ICASE
+ return res->filter((i, v) =>
+ v.word->tolower()->stridx(prefix->tolower()) == 0)
+ endif
+
+ return res->filter((i, v) => v.word->stridx(prefix) == 0)
+ endif
+enddef
+
+# For plugins that implement async completion this function indicates if
+# omnifunc is waiting for LSP response.
+def g:LspOmniCompletePending(): bool
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('completion')
+ return !lspserver->empty() && lspserver.omniCompletePending
+enddef
+
+# Insert mode completion handler. Used when 24x7 completion is enabled
+# (default).
+def LspComplete()
+ var lspserver: dict<any> = buf.CurbufGetServer('completion')
+ if lspserver->empty() || !lspserver.running || !lspserver.ready
+ return
+ endif
+
+ var [triggerKind, triggerChar] = GetTriggerAttributes(lspserver)
+ if triggerKind < 0
+ return
+ endif
+
+ # first send all the changes in the current buffer to the LSP server
+ listener_flush()
+
+ # initiate a request to LSP server to get list of completions
+ lspserver.getCompletion(triggerKind, triggerChar)
+enddef
+
+# Lazy complete documentation handler
+def LspResolve()
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('completion')
+ if lspserver->empty()
+ return
+ endif
+
+ var item = v:event.completed_item
+ if item->has_key('user_data') && !item.user_data->empty()
+ if item.user_data->type() == v:t_dict && !item.user_data->has_key('documentation')
+ lspserver.resolveCompletion(item.user_data)
+ else
+ ShowCompletionDocumentation(item.user_data)
+ endif
+ endif
+enddef
+
+# If the completion popup documentation window displays "markdown" content,
+# then set the 'filetype' to "lspgfm".
+def LspSetPopupFileType()
+ var item = v:event.completed_item
+ var cItem = item->get('user_data', {})
+ if cItem->empty()
+ return
+ endif
+
+ if cItem->type() != v:t_dict || !cItem->has_key('documentation')
+ \ || cItem.documentation->type() != v:t_dict
+ \ || cItem.documentation.kind != 'markdown'
+ return
+ endif
+
+ var id = popup_findinfo()
+ if id > 0
+ var bnum = id->winbufnr()
+ setbufvar(bnum, '&ft', 'lspgfm')
+ endif
+enddef
+
+# complete done handler (LSP server-initiated actions after completion)
+def LspCompleteDone(bnr: number)
+ var lspserver: dict<any> = buf.BufLspServerGet(bnr, 'completion')
+ if lspserver->empty()
+ return
+ endif
+
+ if v:completed_item->type() != v:t_dict
+ return
+ endif
+
+ var completionData: any = v:completed_item->get('user_data', '')
+ if completionData->type() != v:t_dict
+ || !opt.lspOptions.completionTextEdit
+ return
+ endif
+
+ if !completionData->has_key('additionalTextEdits')
+ # Some language servers (e.g. typescript) delay the computation of the
+ # additional text edits. So try to resolve the completion item now to get
+ # the text edits.
+ completionData = lspserver.resolveCompletion(completionData, true)
+ endif
+ if !completionData->get('additionalTextEdits', {})->empty()
+ textedit.ApplyTextEdits(bnr, completionData.additionalTextEdits)
+ endif
+
+ if completionData->has_key('command')
+ # Some language servers (e.g. haskell-language-server) want to apply
+ # additional commands after completion.
+ codeaction.DoCommand(lspserver, completionData.command)
+ endif
+
+enddef
+
+# Initialize buffer-local completion options and autocmds
+export def BufferInit(lspserver: dict<any>, bnr: number, ftype: string)
+ if !lspserver.isCompletionProvider
+ # no support for completion
+ return
+ endif
+
+ if !opt.lspOptions.autoComplete && !LspOmniComplEnabled(ftype) && !opt.lspOptions.omniComplete
+ # LSP auto/omni completion support is not enabled for this buffer
+ return
+ endif
+
+ # buffer-local autocmds for completion
+ var acmds: list<dict<any>> = []
+
+ # set options for insert mode completion
+ if opt.lspOptions.autoComplete
+ if lspserver.completionLazyDoc
+ setbufvar(bnr, '&completeopt', 'menuone,popuphidden,noinsert,noselect')
+ else
+ setbufvar(bnr, '&completeopt', 'menuone,popup,noinsert,noselect')
+ endif
+ setbufvar(bnr, '&completepopup',
+ 'width:80,highlight:Pmenu,align:item,border:off')
+ # <Enter> in insert mode stops completion and inserts a <Enter>
+ if !opt.lspOptions.noNewlineInCompletion
+ :inoremap <expr> <buffer> <CR> pumvisible() ? "\<C-Y>\<CR>" : "\<CR>"
+ endif
+
+ # Trigger 24x7 insert mode completion when text is changed
+ acmds->add({bufnr: bnr,
+ event: 'TextChangedI',
+ group: 'LSPBufferAutocmds',
+ cmd: 'LspComplete()'})
+ endif
+
+ if LspOmniComplEnabled(ftype)
+ setbufvar(bnr, '&omnifunc', 'g:LspOmniFunc')
+ endif
+
+ if lspserver.completionLazyDoc
+ # resolve additional documentation for a selected item
+ acmds->add({bufnr: bnr,
+ event: 'CompleteChanged',
+ group: 'LSPBufferAutocmds',
+ cmd: 'LspResolve()'})
+ endif
+
+ acmds->add({bufnr: bnr,
+ event: 'CompleteChanged',
+ group: 'LSPBufferAutocmds',
+ cmd: 'LspSetPopupFileType()'})
+
+ # Execute LSP server initiated text edits after completion
+ acmds->add({bufnr: bnr,
+ event: 'CompleteDone',
+ group: 'LSPBufferAutocmds',
+ cmd: $'LspCompleteDone({bnr})'})
+
+ autocmd_add(acmds)
+enddef
+
+# Buffer "bnr" is loaded in a window. If omni-completion is enabled for this
+# buffer, then set the 'omnifunc' option.
+export def BufferLoadedInWin(bnr: number)
+ if !opt.lspOptions.autoComplete
+ && LspOmniComplEnabled(bnr->getbufvar('&filetype'))
+ setbufvar(bnr, '&omnifunc', 'g:LspOmniFunc')
+ endif
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/diag.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/diag.vim
new file mode 100644
index 0000000..6975edf
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/diag.vim
@@ -0,0 +1,920 @@
+vim9script
+
+# Functions related to handling LSP diagnostics.
+
+import './options.vim' as opt
+import './buffer.vim' as buf
+import './util.vim'
+
+# [bnr] = {
+# serverDiagnostics: {
+# lspServer1Id: [diag, diag, diag]
+# lspServer2Id: [diag, diag, diag]
+# },
+# serverDiagnosticsByLnum: {
+# lspServer1Id: { [lnum]: [diag, diag diag] },
+# lspServer2Id: { [lnum]: [diag, diag diag] },
+# },
+# sortedDiagnostics: [lspServer1.diags, ...lspServer2.diags]->sort()
+# }
+var diagsMap: dict<dict<any>> = {}
+
+# Initialize the signs and the text property type used for diagnostics.
+export def InitOnce()
+ # Signs and their highlight groups used for LSP diagnostics
+ hlset([
+ {name: 'LspDiagLine', default: true, linksto: 'NONE'},
+ {name: 'LspDiagSignErrorText', default: true, linksto: 'ErrorMsg'},
+ {name: 'LspDiagSignWarningText', default: true, linksto: 'Search'},
+ {name: 'LspDiagSignInfoText', default: true, linksto: 'Pmenu'},
+ {name: 'LspDiagSignHintText', default: true, linksto: 'Question'}
+ ])
+ sign_define([
+ {
+ name: 'LspDiagError',
+ text: opt.lspOptions.diagSignErrorText,
+ texthl: 'LspDiagSignErrorText',
+ linehl: 'LspDiagLine'
+ },
+ {
+ name: 'LspDiagWarning',
+ text: opt.lspOptions.diagSignWarningText,
+ texthl: 'LspDiagSignWarningText',
+ linehl: 'LspDiagLine'
+ },
+ {
+ name: 'LspDiagInfo',
+ text: opt.lspOptions.diagSignInfoText,
+ texthl: 'LspDiagSignInfoText',
+ linehl: 'LspDiagLine'
+ },
+ {
+ name: 'LspDiagHint',
+ text: opt.lspOptions.diagSignHintText,
+ texthl: 'LspDiagSignHintText',
+ linehl: 'LspDiagLine'
+ }
+ ])
+
+ # Diag inline highlight groups and text property types
+ hlset([
+ {name: 'LspDiagInlineError', default: true, linksto: 'SpellBad'},
+ {name: 'LspDiagInlineWarning', default: true, linksto: 'SpellCap'},
+ {name: 'LspDiagInlineInfo', default: true, linksto: 'SpellRare'},
+ {name: 'LspDiagInlineHint', default: true, linksto: 'SpellLocal'}
+ ])
+
+ var override = &cursorline
+ && &cursorlineopt =~ '\<line\>\|\<screenline\>\|\<both\>'
+
+ prop_type_add('LspDiagInlineError',
+ {highlight: 'LspDiagInlineError',
+ priority: 10,
+ override: override})
+ prop_type_add('LspDiagInlineWarning',
+ {highlight: 'LspDiagInlineWarning',
+ priority: 9,
+ override: override})
+ prop_type_add('LspDiagInlineInfo',
+ {highlight: 'LspDiagInlineInfo',
+ priority: 8,
+ override: override})
+ prop_type_add('LspDiagInlineHint',
+ {highlight: 'LspDiagInlineHint',
+ priority: 7,
+ override: override})
+
+ # Diag virtual text highlight groups and text property types
+ hlset([
+ {name: 'LspDiagVirtualTextError', default: true, linksto: 'SpellBad'},
+ {name: 'LspDiagVirtualTextWarning', default: true, linksto: 'SpellCap'},
+ {name: 'LspDiagVirtualTextInfo', default: true, linksto: 'SpellRare'},
+ {name: 'LspDiagVirtualTextHint', default: true, linksto: 'SpellLocal'},
+ ])
+ prop_type_add('LspDiagVirtualTextError',
+ {highlight: 'LspDiagVirtualTextError', override: true})
+ prop_type_add('LspDiagVirtualTextWarning',
+ {highlight: 'LspDiagVirtualTextWarning', override: true})
+ prop_type_add('LspDiagVirtualTextInfo',
+ {highlight: 'LspDiagVirtualTextInfo', override: true})
+ prop_type_add('LspDiagVirtualTextHint',
+ {highlight: 'LspDiagVirtualTextHint', override: true})
+
+ autocmd_add([{group: 'LspCmds',
+ event: 'User',
+ pattern: 'LspOptionsChanged',
+ cmd: 'LspDiagsOptionsChanged()'}])
+
+ # ALE plugin support
+ if opt.lspOptions.aleSupport
+ opt.lspOptions.autoHighlightDiags = false
+ autocmd_add([
+ {
+ group: 'LspAleCmds',
+ event: 'User',
+ pattern: 'ALEWantResults',
+ cmd: 'AleHook(g:ale_want_results_buffer)'
+ }
+ ])
+ endif
+enddef
+
+# Initialize the diagnostics features for the buffer 'bnr'
+export def BufferInit(lspserver: dict<any>, bnr: number)
+ if opt.lspOptions.showDiagInBalloon
+ :set ballooneval balloonevalterm
+ setbufvar(bnr, '&balloonexpr', 'g:LspDiagExpr()')
+ endif
+
+ var acmds: list<dict<any>> = []
+ # Show diagnostics on the status line
+ if opt.lspOptions.showDiagOnStatusLine
+ acmds->add({bufnr: bnr,
+ event: 'CursorMoved',
+ group: 'LSPBufferAutocmds',
+ cmd: 'ShowCurrentDiagInStatusLine()'})
+ endif
+ autocmd_add(acmds)
+enddef
+
+# Function to sort the diagnostics in ascending order based on the line and
+# character offset
+def DiagsSortFunc(a: dict<any>, b: dict<any>): number
+ var a_start: dict<number> = a.range.start
+ var b_start: dict<number> = b.range.start
+ var linediff: number = a_start.line - b_start.line
+ if linediff == 0
+ return a_start.character - b_start.character
+ endif
+ return linediff
+enddef
+
+# Sort diagnostics ascending based on line and character offset
+def SortDiags(diags: list<dict<any>>): list<dict<any>>
+ return diags->sort(DiagsSortFunc)
+enddef
+
+# Remove the diagnostics stored for buffer "bnr"
+export def DiagRemoveFile(bnr: number)
+ if diagsMap->has_key(bnr)
+ diagsMap->remove(bnr)
+ endif
+enddef
+
+def DiagSevToSignName(severity: number): string
+ var typeMap: list<string> = ['LspDiagError', 'LspDiagWarning',
+ 'LspDiagInfo', 'LspDiagHint']
+ if severity > 4
+ return 'LspDiagHint'
+ endif
+ return typeMap[severity - 1]
+enddef
+
+def DiagSevToInlineHLName(severity: number): string
+ var typeMap: list<string> = [
+ 'LspDiagInlineError',
+ 'LspDiagInlineWarning',
+ 'LspDiagInlineInfo',
+ 'LspDiagInlineHint'
+ ]
+ if severity > 4
+ return 'LspDiagInlineHint'
+ endif
+ return typeMap[severity - 1]
+enddef
+
+def DiagSevToVirtualTextHLName(severity: number): string
+ var typeMap: list<string> = [
+ 'LspDiagVirtualTextError',
+ 'LspDiagVirtualTextWarning',
+ 'LspDiagVirtualTextInfo',
+ 'LspDiagVirtualTextHint'
+ ]
+ if severity > 4
+ return 'LspDiagVirtualTextHint'
+ endif
+ return typeMap[severity - 1]
+enddef
+
+def DiagSevToSymbolText(severity: number): string
+ var lspOpts = opt.lspOptions
+ var typeMap: list<string> = [
+ lspOpts.diagSignErrorText,
+ lspOpts.diagSignWarningText,
+ lspOpts.diagSignInfoText,
+ lspOpts.diagSignHintText
+ ]
+ if severity > 4
+ return lspOpts.diagSignHintText
+ endif
+ return typeMap[severity - 1]
+enddef
+
+# Remove signs and text properties for diagnostics in buffer
+def RemoveDiagVisualsForBuffer(bnr: number, all: bool = false)
+ var lspOpts = opt.lspOptions
+ if lspOpts.showDiagWithSign || all
+ # Remove all the existing diagnostic signs
+ sign_unplace('LSPDiag', {buffer: bnr})
+ endif
+
+ if lspOpts.showDiagWithVirtualText || all
+ # Remove all the existing virtual text
+ prop_remove({type: 'LspDiagVirtualTextError', bufnr: bnr, all: true})
+ prop_remove({type: 'LspDiagVirtualTextWarning', bufnr: bnr, all: true})
+ prop_remove({type: 'LspDiagVirtualTextInfo', bufnr: bnr, all: true})
+ prop_remove({type: 'LspDiagVirtualTextHint', bufnr: bnr, all: true})
+ endif
+
+ if lspOpts.highlightDiagInline || all
+ # Remove all the existing virtual text
+ prop_remove({type: 'LspDiagInlineError', bufnr: bnr, all: true})
+ prop_remove({type: 'LspDiagInlineWarning', bufnr: bnr, all: true})
+ prop_remove({type: 'LspDiagInlineInfo', bufnr: bnr, all: true})
+ prop_remove({type: 'LspDiagInlineHint', bufnr: bnr, all: true})
+ endif
+enddef
+
+# Refresh the placed diagnostics in buffer "bnr"
+# This inline signs, inline props, and virtual text diagnostics
+export def DiagsRefresh(bnr: number, all: bool = false)
+ var lspOpts = opt.lspOptions
+ if !lspOpts.autoHighlightDiags
+ return
+ endif
+
+ :silent! bnr->bufload()
+
+ RemoveDiagVisualsForBuffer(bnr, all)
+
+ if !diagsMap->has_key(bnr) ||
+ diagsMap[bnr].sortedDiagnostics->empty()
+ return
+ endif
+
+ # Initialize default/fallback properties for diagnostic virtual text:
+ var diag_align: string = 'above'
+ var diag_wrap: string = 'truncate'
+ var diag_symbol: string = '┌─'
+
+ if lspOpts.diagVirtualTextAlign == 'below'
+ diag_align = 'below'
+ diag_wrap = 'truncate'
+ diag_symbol = '└─'
+ elseif lspOpts.diagVirtualTextAlign == 'after'
+ diag_align = 'after'
+ diag_wrap = 'wrap'
+ diag_symbol = 'E>'
+ endif
+
+ if lspOpts.diagVirtualTextWrap != 'default'
+ diag_wrap = lspOpts.diagVirtualTextWrap
+ endif
+
+ var signs: list<dict<any>> = []
+ var diags: list<dict<any>> = diagsMap[bnr].sortedDiagnostics
+ var inlineHLprops: list<list<list<number>>> = [[], [], [], [], []]
+ for diag in diags
+ # TODO: prioritize most important severity if there are multiple
+ # diagnostics from the same line
+ var d_range = diag.range
+ var d_start = d_range.start
+ var d_end = d_range.end
+ var lnum = d_start.line + 1
+ if lspOpts.showDiagWithSign
+ signs->add({id: 0, buffer: bnr, group: 'LSPDiag',
+ lnum: lnum, name: DiagSevToSignName(diag.severity),
+ priority: 10 - diag.severity})
+ endif
+
+ try
+ if lspOpts.highlightDiagInline
+ var propLocation: list<number> = [
+ lnum, util.GetLineByteFromPos(bnr, d_start) + 1,
+ d_end.line + 1, util.GetLineByteFromPos(bnr, d_end) + 1
+ ]
+ inlineHLprops[diag.severity]->add(propLocation)
+ endif
+
+ if lspOpts.showDiagWithVirtualText
+ var padding: number
+ var symbol: string = diag_symbol
+
+ if diag_align == 'after'
+ padding = 3
+ symbol = DiagSevToSymbolText(diag.severity)
+ else
+ var charIdx = util.GetCharIdxWithoutCompChar(bnr, d_start)
+ padding = charIdx
+ if padding > 0
+ padding = strdisplaywidth(getline(lnum)[ : charIdx - 1])
+ endif
+ endif
+
+ prop_add(lnum, 0, {bufnr: bnr,
+ type: DiagSevToVirtualTextHLName(diag.severity),
+ text: $'{symbol} {diag.message}',
+ text_align: diag_align,
+ text_wrap: diag_wrap,
+ text_padding_left: padding})
+ endif
+ catch /E966\|E964/ # Invalid lnum | Invalid col
+ # Diagnostics arrive asynchronously and the document changed while they
+ # were in transit. Ignore this as new once will arrive shortly.
+ endtry
+ endfor
+
+ if lspOpts.highlightDiagInline
+ for i in range(1, 4)
+ if !inlineHLprops[i]->empty()
+ try
+ prop_add_list({bufnr: bnr, type: DiagSevToInlineHLName(i)},
+ inlineHLprops[i])
+ catch /E966\|E964/ # Invalid lnum | Invalid col
+ endtry
+ endif
+ endfor
+ endif
+
+ if lspOpts.showDiagWithSign
+ signs->sign_placelist()
+ endif
+enddef
+
+# Sends diagnostics to Ale
+def SendAleDiags(bnr: number, timerid: number)
+ if !diagsMap->has_key(bnr)
+ return
+ endif
+
+ # Convert to Ale's diagnostics format (:h ale-loclist-format)
+ ale#other_source#ShowResults(bnr, 'lsp',
+ diagsMap[bnr].sortedDiagnostics->mapnew((_, v) => {
+ return {text: v.message,
+ lnum: v.range.start.line + 1,
+ col: util.GetLineByteFromPos(bnr, v.range.start) + 1,
+ end_lnum: v.range.end.line + 1,
+ end_col: util.GetLineByteFromPos(bnr, v.range.end) + 1,
+ type: "EWIH"[v.severity - 1]}
+ })
+ )
+enddef
+
+# Hook called when Ale wants to retrieve new diagnostics
+def AleHook(bnr: number)
+ ale#other_source#StartChecking(bnr, 'lsp')
+ timer_start(0, function('SendAleDiags', [bnr]))
+enddef
+
+# New LSP diagnostic messages received from the server for a file.
+# Update the signs placed in the buffer for this file
+export def ProcessNewDiags(bnr: number)
+ DiagsUpdateLocList(bnr)
+
+ var lspOpts = opt.lspOptions
+ if lspOpts.aleSupport
+ SendAleDiags(bnr, -1)
+ endif
+
+ if bnr == -1 || !diagsMap->has_key(bnr)
+ return
+ endif
+
+ var curmode: string = mode()
+ if curmode == 'i' || curmode == 'R' || curmode == 'Rv'
+ # postpone placing signs in insert mode and replace mode. These will be
+ # placed after the user returns to Normal mode.
+ setbufvar(bnr, 'LspDiagsUpdatePending', true)
+ return
+ endif
+
+ DiagsRefresh(bnr)
+enddef
+
+# process a diagnostic notification message from the LSP server
+# Notification: textDocument/publishDiagnostics
+# Param: PublishDiagnosticsParams
+export def DiagNotification(lspserver: dict<any>, uri: string, diags_arg: list<dict<any>>): void
+ # Diagnostics are disabled for this server?
+ if !lspserver.featureEnabled('diagnostics')
+ return
+ endif
+
+ var fname: string = util.LspUriToFile(uri)
+ var bnr: number = fname->bufnr()
+ if bnr == -1
+ # Is this condition possible?
+ return
+ endif
+
+ var newDiags: list<dict<any>> = diags_arg
+
+ if lspserver.needOffsetEncoding
+ # Decode the position encoding in all the diags
+ newDiags->map((_, dval) => {
+ lspserver.decodeRange(bnr, dval.range)
+ return dval
+ })
+ endif
+
+ if lspserver.processDiagHandler != null_function
+ newDiags = lspserver.processDiagHandler(diags_arg)
+ endif
+
+ # TODO: Is the buffer (bnr) always a loaded buffer? Should we load it here?
+ var lastlnum: number = bnr->getbufinfo()[0].linecount
+
+ # store the diagnostic for each line separately
+ var diagsByLnum: dict<list<dict<any>>> = {}
+
+ var diagWithinRange: list<dict<any>> = []
+ for diag in newDiags
+ var d_start = diag.range.start
+ if d_start.line + 1 > lastlnum
+ # Make sure the line number is a valid buffer line number
+ d_start.line = lastlnum - 1
+ endif
+
+ var lnum = d_start.line + 1
+ if !diagsByLnum->has_key(lnum)
+ diagsByLnum[lnum] = []
+ endif
+ diagsByLnum[lnum]->add(diag)
+
+ diagWithinRange->add(diag)
+ endfor
+
+ var serverDiags: dict<list<any>> = diagsMap->has_key(bnr) ?
+ diagsMap[bnr].serverDiagnostics : {}
+ serverDiags[lspserver.id] = diagWithinRange
+
+ var serverDiagsByLnum: dict<dict<list<any>>> = diagsMap->has_key(bnr) ?
+ diagsMap[bnr].serverDiagnosticsByLnum : {}
+ serverDiagsByLnum[lspserver.id] = diagsByLnum
+
+ # store the diagnostic for each line separately
+ var joinedServerDiags: list<dict<any>> = []
+ for diags in serverDiags->values()
+ joinedServerDiags->extend(diags)
+ endfor
+
+ var sortedDiags = SortDiags(joinedServerDiags)
+
+ diagsMap[bnr] = {
+ sortedDiagnostics: sortedDiags,
+ serverDiagnosticsByLnum: serverDiagsByLnum,
+ serverDiagnostics: serverDiags
+ }
+
+ ProcessNewDiags(bnr)
+
+ # Notify user scripts that diags has been updated
+ if exists('#User#LspDiagsUpdated')
+ :doautocmd <nomodeline> User LspDiagsUpdated
+ endif
+enddef
+
+# get the count of error in the current buffer
+export def DiagsGetErrorCount(bnr: number): dict<number>
+ var diagSevCount: list<number> = [0, 0, 0, 0, 0]
+ if diagsMap->has_key(bnr)
+ var diags = diagsMap[bnr].sortedDiagnostics
+ for diag in diags
+ var severity = diag->get('severity', 0)
+ diagSevCount[severity] += 1
+ endfor
+ endif
+
+ return {
+ Error: diagSevCount[1],
+ Warn: diagSevCount[2],
+ Info: diagSevCount[3],
+ Hint: diagSevCount[4]
+ }
+enddef
+
+# Map the LSP DiagnosticSeverity to a quickfix type character
+def DiagSevToQfType(severity: number): string
+ var typeMap: list<string> = ['E', 'W', 'I', 'N']
+
+ if severity > 4
+ return ''
+ endif
+
+ return typeMap[severity - 1]
+enddef
+
+# Update the location list window for the current window with the diagnostic
+# messages.
+# Returns true if diagnostics is not empty and false if it is empty.
+def DiagsUpdateLocList(bnr: number, calledByCmd: bool = false): bool
+ var fname: string = bnr->bufname()->fnamemodify(':p')
+ if fname->empty()
+ return false
+ endif
+
+ var LspQfId: number = bnr->getbufvar('LspQfId', 0)
+ if LspQfId == 0 && !opt.lspOptions.autoPopulateDiags && !calledByCmd
+ # Diags location list is not present. Create the location list only if
+ # the 'autoPopulateDiags' option is set or the ":LspDiag show" command is
+ # invoked.
+ return false
+ endif
+
+ if LspQfId != 0 && getloclist(0, {id: LspQfId}).id != LspQfId
+ # Previously used location list for the diagnostics is gone
+ LspQfId = 0
+ endif
+
+ if !diagsMap->has_key(bnr) ||
+ diagsMap[bnr].sortedDiagnostics->empty()
+ if LspQfId != 0
+ setloclist(0, [], 'r', {id: LspQfId, items: []})
+ endif
+ return false
+ endif
+
+ var qflist: list<dict<any>> = []
+ var text: string
+
+ var diags = diagsMap[bnr].sortedDiagnostics
+ for diag in diags
+ var d_range = diag.range
+ var d_start = d_range.start
+ var d_end = d_range.end
+ text = diag.message->substitute("\n\\+", "\n", 'g')
+ qflist->add({filename: fname,
+ lnum: d_start.line + 1,
+ col: util.GetLineByteFromPos(bnr, d_start) + 1,
+ end_lnum: d_end.line + 1,
+ end_col: util.GetLineByteFromPos(bnr, d_end) + 1,
+ text: text,
+ type: DiagSevToQfType(diag.severity)})
+ endfor
+
+ var op: string = ' '
+ var props = {title: 'Language Server Diagnostics', items: qflist}
+ if LspQfId != 0
+ op = 'r'
+ props.id = LspQfId
+ endif
+ setloclist(0, [], op, props)
+ if LspQfId == 0
+ setbufvar(bnr, 'LspQfId', getloclist(0, {id: 0}).id)
+ endif
+
+ return true
+enddef
+
+# Display the diagnostic messages from the LSP server for the current buffer
+# in a location list
+export def ShowAllDiags(): void
+ var bnr: number = bufnr()
+ if !DiagsUpdateLocList(bnr, true)
+ util.WarnMsg($'No diagnostic messages found for {@%}')
+ return
+ endif
+
+ var save_winid = win_getid()
+ # make the diagnostics error list the active one and open it
+ var LspQfId: number = bnr->getbufvar('LspQfId', 0)
+ var LspQfNr: number = getloclist(0, {id: LspQfId, nr: 0}).nr
+ exe $':{LspQfNr} lhistory'
+ :lopen
+ if !opt.lspOptions.keepFocusInDiags
+ save_winid->win_gotoid()
+ endif
+enddef
+
+# Display the message of "diag" in a popup window right below the position in
+# the diagnostic message.
+def ShowDiagInPopup(diag: dict<any>)
+ var d_start = diag.range.start
+ var dlnum = d_start.line + 1
+ var ltext = dlnum->getline()
+ var dlcol = ltext->byteidxcomp(d_start.character) + 1
+
+ var lastline = line('$')
+ if dlnum > lastline
+ # The line number is outside the last line in the file.
+ dlnum = lastline
+ endif
+ if dlcol < 1
+ # The column is outside the last character in line.
+ dlcol = ltext->len() + 1
+ endif
+ var d = screenpos(0, dlnum, dlcol)
+ if d->empty()
+ # If the diag position cannot be converted to Vim lnum/col, then use
+ # the current cursor position
+ d = {row: line('.'), col: col('.')}
+ endif
+
+ # Display a popup right below the diagnostics position
+ var msg = diag.message->split("\n")
+ var msglen = msg->reduce((acc, val) => max([acc, val->strcharlen()]), 0)
+
+ var ppopts = {}
+ ppopts.pos = 'topleft'
+ ppopts.line = d.row + 1
+ ppopts.moved = 'any'
+
+ if msglen > &columns
+ ppopts.wrap = true
+ ppopts.col = 1
+ else
+ ppopts.wrap = false
+ ppopts.col = d.col
+ endif
+
+ popup_create(msg, ppopts)
+enddef
+
+# Display the "diag" message in a popup or in the status message area
+def DisplayDiag(diag: dict<any>)
+ if opt.lspOptions.showDiagInPopup
+ # Display the diagnostic message in a popup window.
+ ShowDiagInPopup(diag)
+ else
+ # Display the diagnostic message in the status message area
+ :echo diag.message
+ endif
+enddef
+
+# Show the diagnostic message for the current line
+export def ShowCurrentDiag(atPos: bool)
+ var bnr: number = bufnr()
+ var lnum: number = line('.')
+ var col: number = charcol('.')
+ var diag: dict<any> = GetDiagByPos(bnr, lnum, col, atPos)
+ if diag->empty()
+ util.WarnMsg($'No diagnostic messages found for current {atPos ? "position" : "line"}')
+ else
+ DisplayDiag(diag)
+ endif
+enddef
+
+# Show the diagnostic message for the current line without linebreak
+def ShowCurrentDiagInStatusLine()
+ var bnr: number = bufnr()
+ var lnum: number = line('.')
+ var col: number = charcol('.')
+ var diag: dict<any> = GetDiagByPos(bnr, lnum, col)
+ if !diag->empty()
+ # 15 is a enough length not to cause line break
+ var max_width = &columns - 15
+ var code = ''
+ if diag->has_key('code')
+ code = $'[{diag.code}] '
+ endif
+ var msgNoLineBreak = code ..
+ diag.message->substitute("\n", ' ', '')->substitute("\\n", ' ', '')
+ :echo msgNoLineBreak[ : max_width]
+ else
+ # clear the previous message
+ :echo ''
+ endif
+enddef
+
+# Get the diagnostic from the LSP server for a particular line and character
+# offset in a file
+export def GetDiagByPos(bnr: number, lnum: number, col: number,
+ atPos: bool = false): dict<any>
+ var diags_in_line = GetDiagsByLine(bnr, lnum)
+
+ for diag in diags_in_line
+ var r = diag.range
+ var startCharIdx = util.GetCharIdxWithoutCompChar(bnr, r.start)
+ var endCharIdx = util.GetCharIdxWithoutCompChar(bnr, r.end)
+ if atPos
+ if col >= startCharIdx + 1 && col < endCharIdx + 1
+ return diag
+ endif
+ elseif col <= startCharIdx + 1
+ return diag
+ endif
+ endfor
+
+ # No diagnostic to the right of the position, return the last one instead
+ if !atPos && diags_in_line->len() > 0
+ return diags_in_line[-1]
+ endif
+
+ return {}
+enddef
+
+# Get all diagnostics from the LSP server for a particular line in a file
+export def GetDiagsByLine(bnr: number, lnum: number, lspserver: dict<any> = null_dict): list<dict<any>>
+ if !diagsMap->has_key(bnr)
+ return []
+ endif
+
+ var diags: list<dict<any>> = []
+
+ var serverDiagsByLnum = diagsMap[bnr].serverDiagnosticsByLnum
+
+ if lspserver == null_dict
+ for diagsByLnum in serverDiagsByLnum->values()
+ if diagsByLnum->has_key(lnum)
+ diags->extend(diagsByLnum[lnum])
+ endif
+ endfor
+ else
+ if !serverDiagsByLnum->has_key(lspserver.id)
+ return []
+ endif
+ if serverDiagsByLnum[lspserver.id]->has_key(lnum)
+ diags = serverDiagsByLnum[lspserver.id][lnum]
+ endif
+ endif
+
+ return diags->sort((a, b) => {
+ return a.range.start.character - b.range.start.character
+ })
+enddef
+
+# Utility function to do the actual jump
+def JumpDiag(diag: dict<any>)
+ var startPos: dict<number> = diag.range.start
+ setcursorcharpos(startPos.line + 1,
+ util.GetCharIdxWithoutCompChar(bufnr(), startPos) + 1)
+ :normal! zv
+ if !opt.lspOptions.showDiagWithVirtualText
+ :redraw
+ DisplayDiag(diag)
+ endif
+enddef
+
+# jump to the next/previous/first diagnostic message in the current buffer
+export def LspDiagsJump(which: string, a_count: number = 0): void
+ var fname: string = expand('%:p')
+ if fname->empty()
+ return
+ endif
+ var bnr: number = bufnr()
+
+ if !diagsMap->has_key(bnr) ||
+ diagsMap[bnr].sortedDiagnostics->empty()
+ util.WarnMsg($'No diagnostic messages found for {fname}')
+ return
+ endif
+
+ var diags = diagsMap[bnr].sortedDiagnostics
+
+ if which == 'first'
+ JumpDiag(diags[0])
+ return
+ endif
+
+ if which == 'last'
+ JumpDiag(diags[-1])
+ return
+ endif
+
+ # Find the entry just before the current line (binary search)
+ var count = a_count > 1 ? a_count : 1
+ var curlnum: number = line('.')
+ var curcol: number = charcol('.')
+ for diag in (which == 'next' || which == 'nextWrap' || which == 'here') ?
+ diags : diags->copy()->reverse()
+ var d_start = diag.range.start
+ var lnum = d_start.line + 1
+ var col = util.GetCharIdxWithoutCompChar(bnr, d_start) + 1
+ if ((which == 'next' || which == 'nextWrap') && (lnum > curlnum || lnum == curlnum && col > curcol))
+ || ((which == 'prev' || which == 'prevWrap') && (lnum < curlnum || lnum == curlnum
+ && col < curcol))
+ || (which == 'here' && (lnum == curlnum && col >= curcol))
+
+ # Skip over as many diags as "count" dictates
+ count = count - 1
+ if count > 0
+ continue
+ endif
+
+ JumpDiag(diag)
+ return
+ endif
+ endfor
+
+ # If [count] exceeded the remaining diags
+ if ((which == 'next' || which == 'nextWrap') && a_count > 1 && a_count != count)
+ JumpDiag(diags[-1])
+ return
+ endif
+
+ # If [count] exceeded the previous diags
+ if ((which == 'prev' || which == 'prevWrap') && a_count > 1 && a_count != count)
+ JumpDiag(diags[0])
+ return
+ endif
+
+ if which == 'nextWrap' || which == 'prevWrap'
+ JumpDiag(diags[which == 'nextWrap' ? 0 : -1])
+ return
+ endif
+
+ if which == 'here'
+ util.WarnMsg('No more diagnostics found on this line')
+ else
+ util.WarnMsg('No more diagnostics found')
+ endif
+enddef
+
+# Return the sorted diagnostics for buffer "bnr". Default is the current
+# buffer. A copy of the diagnostics is returned so that the caller can modify
+# the diagnostics.
+export def GetDiagsForBuf(bnr: number = bufnr()): list<dict<any>>
+ if !diagsMap->has_key(bnr) ||
+ diagsMap[bnr].sortedDiagnostics->empty()
+ return []
+ endif
+
+ return diagsMap[bnr].sortedDiagnostics->deepcopy()
+enddef
+
+# Return the diagnostic text from the LSP server for the current mouse line to
+# display in a balloon
+def g:LspDiagExpr(): any
+ if !opt.lspOptions.showDiagInBalloon
+ return ''
+ endif
+
+ var diagsInfo: list<dict<any>> =
+ GetDiagsByLine(v:beval_bufnr, v:beval_lnum)
+ if diagsInfo->empty()
+ # No diagnostic for the current cursor location
+ return ''
+ endif
+ var diagFound: dict<any> = {}
+ for diag in diagsInfo
+ var r = diag.range
+ var startcol = util.GetLineByteFromPos(v:beval_bufnr, r.start) + 1
+ var endcol = util.GetLineByteFromPos(v:beval_bufnr, r.end) + 1
+ if v:beval_col >= startcol && v:beval_col < endcol
+ diagFound = diag
+ break
+ endif
+ endfor
+ if diagFound->empty()
+ # mouse is outside of the diagnostics range
+ return ''
+ endif
+
+ # return the found diagnostic
+ return diagFound.message->split("\n")
+enddef
+
+# Track the current diagnostics auto highlight enabled/disabled state. Used
+# when the "autoHighlightDiags" option value is changed.
+var save_autoHighlightDiags = opt.lspOptions.autoHighlightDiags
+var save_highlightDiagInline = opt.lspOptions.highlightDiagInline
+var save_showDiagWithSign = opt.lspOptions.showDiagWithSign
+var save_showDiagWithVirtualText = opt.lspOptions.showDiagWithVirtualText
+
+# Enable the LSP diagnostics highlighting
+export def DiagsHighlightEnable()
+ opt.lspOptions.autoHighlightDiags = true
+ save_autoHighlightDiags = true
+ for binfo in getbufinfo({bufloaded: true})
+ if diagsMap->has_key(binfo.bufnr)
+ DiagsRefresh(binfo.bufnr)
+ endif
+ endfor
+enddef
+
+# Disable the LSP diagnostics highlighting in all the buffers
+export def DiagsHighlightDisable()
+ # turn off all diags highlight
+ opt.lspOptions.autoHighlightDiags = false
+ save_autoHighlightDiags = false
+ for binfo in getbufinfo()
+ if diagsMap->has_key(binfo.bufnr)
+ RemoveDiagVisualsForBuffer(binfo.bufnr)
+ endif
+ endfor
+enddef
+
+# Some options are changed. If 'autoHighlightDiags' option is changed, then
+# either enable or disable diags auto highlight.
+export def LspDiagsOptionsChanged()
+ if save_autoHighlightDiags && !opt.lspOptions.autoHighlightDiags
+ DiagsHighlightDisable()
+ elseif !save_autoHighlightDiags && opt.lspOptions.autoHighlightDiags
+ DiagsHighlightEnable()
+ endif
+
+ if save_highlightDiagInline != opt.lspOptions.highlightDiagInline
+ || save_showDiagWithSign != opt.lspOptions.showDiagWithSign
+ || save_showDiagWithVirtualText != opt.lspOptions.showDiagWithVirtualText
+ save_highlightDiagInline = opt.lspOptions.highlightDiagInline
+ save_showDiagWithSign = opt.lspOptions.showDiagWithSign
+ save_showDiagWithVirtualText = opt.lspOptions.showDiagWithVirtualText
+ for binfo in getbufinfo({bufloaded: true})
+ if diagsMap->has_key(binfo.bufnr)
+ DiagsRefresh(binfo.bufnr, true)
+ endif
+ endfor
+ endif
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/handlers.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/handlers.vim
new file mode 100644
index 0000000..dd597ed
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/handlers.vim
@@ -0,0 +1,305 @@
+vim9script
+
+# Handlers for messages from the LSP server
+# Refer to https://microsoft.github.io/language-server-protocol/specification
+# for the Language Server Protocol (LSP) specification.
+
+import './util.vim'
+import './diag.vim'
+import './textedit.vim'
+
+# Process various reply messages from the LSP server
+export def ProcessReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>): void
+ util.ErrMsg($'Unsupported reply received from LSP server: {reply->string()} for request: {req->string()}')
+enddef
+
+# process a diagnostic notification message from the LSP server
+# Notification: textDocument/publishDiagnostics
+# Param: PublishDiagnosticsParams
+def ProcessDiagNotif(lspserver: dict<any>, reply: dict<any>): void
+ var params = reply.params
+ diag.DiagNotification(lspserver, params.uri, params.diagnostics)
+enddef
+
+# Convert LSP message type to a string
+def LspMsgTypeToString(lspMsgType: number): string
+ var msgStrMap: list<string> = ['', 'Error', 'Warning', 'Info', 'Log']
+ var mtype: string = 'Log'
+ if lspMsgType > 0 && lspMsgType < 5
+ mtype = msgStrMap[lspMsgType]
+ endif
+ return mtype
+enddef
+
+# process a show notification message from the LSP server
+# Notification: window/showMessage
+# Param: ShowMessageParams
+def ProcessShowMsgNotif(lspserver: dict<any>, reply: dict<any>)
+ var msgType = reply.params.type
+ if msgType >= 4
+ # ignore log messages from the LSP server (too chatty)
+ # TODO: Add a configuration to control the message level that will be
+ # displayed. Also store these messages and provide a command to display
+ # them.
+ return
+ endif
+ if msgType == 1
+ util.ErrMsg($'Lsp({lspserver.name}) {reply.params.message}')
+ elseif msgType == 2
+ util.WarnMsg($'Lsp({lspserver.name}) {reply.params.message}')
+ elseif msgType == 3
+ util.InfoMsg($'Lsp({lspserver.name}) {reply.params.message}')
+ endif
+enddef
+
+# process a log notification message from the LSP server
+# Notification: window/logMessage
+# Param: LogMessageParams
+def ProcessLogMsgNotif(lspserver: dict<any>, reply: dict<any>)
+ var params = reply.params
+ var mtype = LspMsgTypeToString(params.type)
+ lspserver.addMessage(mtype, params.message)
+enddef
+
+# process the log trace notification messages
+# Notification: $/logTrace
+# Param: LogTraceParams
+def ProcessLogTraceNotif(lspserver: dict<any>, reply: dict<any>)
+ lspserver.addMessage('trace', reply.params.message)
+enddef
+
+# process unsupported notification messages
+def ProcessUnsupportedNotif(lspserver: dict<any>, reply: dict<any>)
+ util.WarnMsg($'Unsupported notification message received from the LSP server ({lspserver.name}), message = {reply->string()}')
+enddef
+
+# Dict to process telemetry notification messages only once per filetype
+var telemetryProcessed: dict<bool> = {}
+# process unsupported notification messages only once
+def ProcessUnsupportedNotifOnce(lspserver: dict<any>, reply: dict<any>)
+ if !telemetryProcessed->get(&ft, false)
+ ProcessUnsupportedNotif(lspserver, reply)
+ telemetryProcessed->extend({[&ft]: true})
+ endif
+enddef
+
+# process notification messages from the LSP server
+export def ProcessNotif(lspserver: dict<any>, reply: dict<any>): void
+ var lsp_notif_handlers: dict<func> =
+ {
+ 'window/showMessage': ProcessShowMsgNotif,
+ 'window/logMessage': ProcessLogMsgNotif,
+ 'textDocument/publishDiagnostics': ProcessDiagNotif,
+ '$/logTrace': ProcessLogTraceNotif,
+ 'telemetry/event': ProcessUnsupportedNotifOnce,
+ }
+
+ # Explicitly ignored notification messages (many of them are specific to a
+ # particular language server)
+ var lsp_ignored_notif_handlers: list<string> =
+ [
+ '$/progress',
+ '$/status/report',
+ '$/status/show',
+ # PHP intelephense server sends the "indexingStarted" and
+ # "indexingEnded" notifications which is not in the LSP specification.
+ 'indexingStarted',
+ 'indexingEnded',
+ # Java language server sends the 'language/status' notification which is
+ # not in the LSP specification.
+ 'language/status',
+ # Typescript language server sends the '$/typescriptVersion'
+ # notification which is not in the LSP specification.
+ '$/typescriptVersion',
+ # Dart language server sends the '$/analyzerStatus' notification which
+ # is not in the LSP specification.
+ '$/analyzerStatus',
+ # pyright language server notifications
+ 'pyright/beginProgress',
+ 'pyright/reportProgress',
+ 'pyright/endProgress',
+ 'eslint/status',
+ 'taplo/didChangeSchemaAssociation',
+ 'sqlLanguageServer.finishSetup',
+ # ccls language server notifications
+ '$ccls/publishSkippedRanges',
+ '$ccls/publishSemanticHighlight',
+ # omnisharp language server notifications
+ 'o#/backgrounddiagnosticstatus',
+ 'o#/msbuildprojectdiagnostics',
+ 'o#/projectadded',
+ 'o#/projectchanged',
+ 'o#/projectconfiguration',
+ 'o#/projectdiagnosticstatus',
+ 'o#/unresolveddependencies',
+ '@/tailwindCSS/projectInitialized'
+ ]
+
+ if lsp_notif_handlers->has_key(reply.method)
+ lsp_notif_handlers[reply.method](lspserver, reply)
+ elseif lspserver.customNotificationHandlers->has_key(reply.method)
+ lspserver.customNotificationHandlers[reply.method](lspserver, reply)
+ elseif lsp_ignored_notif_handlers->index(reply.method) == -1
+ ProcessUnsupportedNotif(lspserver, reply)
+ endif
+enddef
+
+# process the workspace/applyEdit LSP server request
+# Request: "workspace/applyEdit"
+# Param: ApplyWorkspaceEditParams
+def ProcessApplyEditReq(lspserver: dict<any>, request: dict<any>)
+ # interface ApplyWorkspaceEditParams
+ if !request->has_key('params')
+ return
+ endif
+ var workspaceEditParams: dict<any> = request.params
+ if workspaceEditParams->has_key('label')
+ util.InfoMsg($'Workspace edit {workspaceEditParams.label}')
+ endif
+ textedit.ApplyWorkspaceEdit(workspaceEditParams.edit)
+ # TODO: Need to return the proper result of the edit operation
+ lspserver.sendResponse(request, {applied: true}, {})
+enddef
+
+# process the workspace/workspaceFolders LSP server request
+# Request: "workspace/workspaceFolders"
+# Param: none
+def ProcessWorkspaceFoldersReq(lspserver: dict<any>, request: dict<any>)
+ if !lspserver->has_key('workspaceFolders')
+ lspserver.sendResponse(request, null, {})
+ return
+ endif
+ if lspserver.workspaceFolders->empty()
+ lspserver.sendResponse(request, [], {})
+ else
+ lspserver.sendResponse(request,
+ \ lspserver.workspaceFolders->copy()->map('{name: v:val->fnamemodify(":t"), uri: util.LspFileToUri(v:val)}'),
+ \ {})
+ endif
+enddef
+
+# process the workspace/configuration LSP server request
+# Request: "workspace/configuration"
+# Param: ConfigurationParams
+def ProcessWorkspaceConfiguration(lspserver: dict<any>, request: dict<any>)
+ var items = request.params.items
+ var response = items->map((_, item) => lspserver.workspaceConfigGet(item))
+
+ # Server expect null value if no config is given
+ if response->type() == v:t_list && response->len() == 1
+ && response[0]->type() == v:t_dict
+ && response[0] == null_dict
+ response[0] = null
+ endif
+
+ lspserver.sendResponse(request, response, {})
+enddef
+
+# process the window/workDoneProgress/create LSP server request
+# Request: "window/workDoneProgress/create"
+# Param: none
+def ProcessWorkDoneProgressCreate(lspserver: dict<any>, request: dict<any>)
+ lspserver.sendResponse(request, null, {})
+enddef
+
+# process the window/showMessageRequest LSP server request
+# Request: "window/showMessageRequest"
+# Param: ShowMessageRequestParams
+def ProcessShowMessageRequest(lspserver: dict<any>, request: dict<any>)
+ # TODO: for now 'showMessageRequest' handled same like 'showMessage'
+ # regardless 'actions'
+ ProcessShowMsgNotif(lspserver, request)
+ lspserver.sendResponse(request, null, {})
+enddef
+
+# process the client/registerCapability LSP server request
+# Request: "client/registerCapability"
+# Param: RegistrationParams
+def ProcessClientRegisterCap(lspserver: dict<any>, request: dict<any>)
+ lspserver.sendResponse(request, null, {})
+enddef
+
+# process the client/unregisterCapability LSP server request
+# Request: "client/unregisterCapability"
+# Param: UnregistrationParams
+def ProcessClientUnregisterCap(lspserver: dict<any>, request: dict<any>)
+ lspserver.sendResponse(request, null, {})
+enddef
+
+# process a request message from the server
+export def ProcessRequest(lspserver: dict<any>, request: dict<any>)
+ var lspRequestHandlers: dict<func> =
+ {
+ 'client/registerCapability': ProcessClientRegisterCap,
+ 'client/unregisterCapability': ProcessClientUnregisterCap,
+ 'window/workDoneProgress/create': ProcessWorkDoneProgressCreate,
+ 'window/showMessageRequest': ProcessShowMessageRequest,
+ 'workspace/applyEdit': ProcessApplyEditReq,
+ 'workspace/configuration': ProcessWorkspaceConfiguration,
+ 'workspace/workspaceFolders': ProcessWorkspaceFoldersReq
+ # TODO: Handle the following requests from the server:
+ # workspace/codeLens/refresh
+ # workspace/diagnostic/refresh
+ # workspace/inlayHint/refresh
+ # workspace/inlineValue/refresh
+ # workspace/semanticTokens/refresh
+ }
+
+ # Explicitly ignored requests
+ var lspIgnoredRequestHandlers: list<string> =
+ [
+ # Eclipse java language server sends the
+ # 'workspace/executeClientCommand' request (to reload bundles) which is
+ # not in the LSP specification.
+ 'workspace/executeClientCommand',
+ ]
+
+ if lspRequestHandlers->has_key(request.method)
+ lspRequestHandlers[request.method](lspserver, request)
+ elseif lspserver.customRequestHandlers->has_key(request.method)
+ lspserver.customRequestHandlers[request.method](lspserver, request)
+ elseif lspIgnoredRequestHandlers->index(request.method) == -1
+ util.ErrMsg($'Unsupported request message received from the LSP server ({lspserver.name}), message = {request->string()}')
+ endif
+enddef
+
+# process one or more LSP server messages
+export def ProcessMessages(lspserver: dict<any>): void
+ var idx: number
+ var len: number
+ var content: string
+ var msg: dict<any>
+ var req: dict<any>
+
+ msg = lspserver.data
+ if msg->has_key('result') || msg->has_key('error')
+ # response message from the server
+ req = lspserver.requests->get(msg.id->string(), {})
+ if !req->empty()
+ # Remove the corresponding stored request message
+ lspserver.requests->remove(msg.id->string())
+
+ if msg->has_key('result')
+ lspserver.processReply(req, msg)
+ else
+ # request failed
+ var emsg: string = msg.error.message
+ emsg ..= $', code = {msg.error.code}'
+ if msg.error->has_key('data')
+ emsg ..= $', data = {msg.error.data->string()}'
+ endif
+ util.ErrMsg($'request {req.method} failed ({emsg})')
+ endif
+ endif
+ elseif msg->has_key('id') && msg->has_key('method')
+ # request message from the server
+ lspserver.processRequest(msg)
+ elseif msg->has_key('method')
+ # notification message from the server
+ lspserver.processNotif(msg)
+ else
+ util.ErrMsg($'Unsupported message ({msg->string()})')
+ endif
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/hover.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/hover.vim
new file mode 100644
index 0000000..41e7f11
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/hover.vim
@@ -0,0 +1,137 @@
+vim9script
+
+# Functions related to displaying hover symbol information.
+
+import './util.vim'
+import './options.vim' as opt
+
+# Util used to compute the hoverText from textDocument/hover reply
+def GetHoverText(lspserver: dict<any>, hoverResult: any): list<any>
+ if hoverResult->empty()
+ return ['', '']
+ endif
+
+ # MarkupContent
+ if hoverResult.contents->type() == v:t_dict
+ && hoverResult.contents->has_key('kind')
+ if hoverResult.contents.kind == 'plaintext'
+ return [hoverResult.contents.value->split("\n"), 'text']
+ endif
+
+ if hoverResult.contents.kind == 'markdown'
+ return [hoverResult.contents.value->split("\n"), 'lspgfm']
+ endif
+
+ lspserver.errorLog(
+ $'{strftime("%m/%d/%y %T")}: Unsupported hover contents kind ({hoverResult.contents.kind})'
+ )
+ return ['', '']
+ endif
+
+ # MarkedString
+ if hoverResult.contents->type() == v:t_dict
+ && hoverResult.contents->has_key('value')
+ return [
+ [$'``` {hoverResult.contents.language}']
+ + hoverResult.contents.value->split("\n")
+ + ['```'],
+ 'lspgfm'
+ ]
+ endif
+
+ # MarkedString
+ if hoverResult.contents->type() == v:t_string
+ return [hoverResult.contents->split("\n"), 'lspgfm']
+ endif
+
+ # interface MarkedString[]
+ if hoverResult.contents->type() == v:t_list
+ var hoverText: list<string> = []
+ for e in hoverResult.contents
+ if !hoverText->empty()
+ hoverText->extend(['- - -'])
+ endif
+
+ if e->type() == v:t_string
+ hoverText->extend(e->split("\n"))
+ else
+ hoverText->extend([$'``` {e.language}'])
+ hoverText->extend(e.value->split("\n"))
+ hoverText->extend(['```'])
+ endif
+ endfor
+
+ return [hoverText, 'lspgfm']
+ endif
+
+ lspserver.errorLog(
+ $'{strftime("%m/%d/%y %T")}: Unsupported hover reply ({hoverResult})'
+ )
+ return ['', '']
+enddef
+
+# Key filter function for the hover popup window.
+# Only keys to scroll the popup window are supported.
+def HoverWinFilterKey(hoverWin: number, key: string): bool
+ var keyHandled = false
+
+ if key == "\<C-E>"
+ || key == "\<C-D>"
+ || key == "\<C-F>"
+ || key == "\<PageDown>"
+ || key == "\<C-Y>"
+ || key == "\<C-U>"
+ || key == "\<C-B>"
+ || key == "\<PageUp>"
+ || key == "\<C-Home>"
+ || key == "\<C-End>"
+ # scroll the hover popup window
+ win_execute(hoverWin, $'normal! {key}')
+ keyHandled = true
+ endif
+
+ if key == "\<Esc>"
+ hoverWin->popup_close()
+ keyHandled = true
+ endif
+
+ return keyHandled
+enddef
+
+# process the 'textDocument/hover' reply from the LSP server
+# Result: Hover | null
+export def HoverReply(lspserver: dict<any>, hoverResult: any, cmdmods: string): void
+ var [hoverText, hoverKind] = GetHoverText(lspserver, hoverResult)
+
+ # Nothing to show
+ if hoverText->empty()
+ if cmdmods !~ 'silent'
+ util.WarnMsg($'No documentation found for current keyword')
+ endif
+ return
+ endif
+
+ if opt.lspOptions.hoverInPreview
+ execute $':silent! {cmdmods} pedit LspHover'
+ :wincmd P
+ :setlocal buftype=nofile
+ :setlocal bufhidden=delete
+ bufnr()->deletebufline(1, '$')
+ hoverText->append(0)
+ [1, 1]->cursor()
+ exe $'setlocal ft={hoverKind}'
+ :wincmd p
+ else
+ popup_clear()
+ var winid = hoverText->popup_atcursor({moved: 'any',
+ close: 'click',
+ fixed: true,
+ maxwidth: 80,
+ border: [0, 1, 0, 1],
+ borderchars: [' '],
+ filter: HoverWinFilterKey})
+ win_execute(winid, $'setlocal ft={hoverKind}')
+ endif
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/inlayhints.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/inlayhints.vim
new file mode 100644
index 0000000..a793699
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/inlayhints.vim
@@ -0,0 +1,235 @@
+vim9script
+
+# Functions for dealing with inlay hints
+
+import './util.vim'
+import './buffer.vim' as buf
+import './options.vim' as opt
+
+# Initialize the highlight group and the text property type used for
+# inlay hints.
+export def InitOnce()
+ hlset([
+ {name: 'LspInlayHintsType', default: true, linksto: 'Label'},
+ {name: 'LspInlayHintsParam', default: true, linksto: 'Conceal'}
+ ])
+ prop_type_add('LspInlayHintsType', {highlight: 'LspInlayHintsType'})
+ prop_type_add('LspInlayHintsParam', {highlight: 'LspInlayHintsParam'})
+
+ autocmd_add([{group: 'LspCmds',
+ event: 'User',
+ pattern: 'LspOptionsChanged',
+ cmd: 'LspInlayHintsOptionsChanged()'}])
+enddef
+
+# Clear all the inlay hints text properties in the current buffer
+def InlayHintsClear(bnr: number)
+ prop_remove({type: 'LspInlayHintsType', bufnr: bnr, all: true})
+ prop_remove({type: 'LspInlayHintsParam', bufnr: bnr, all: true})
+enddef
+
+# LSP inlay hints reply message handler
+export def InlayHintsReply(lspserver: dict<any>, bnr: number, inlayHints: any)
+ if inlayHints->empty()
+ return
+ endif
+
+ InlayHintsClear(bnr)
+
+ if mode() != 'n'
+ # Update inlay hints only in normal mode
+ return
+ endif
+
+ for hint in inlayHints
+ var label = ''
+ if hint.label->type() == v:t_list
+ label = hint.label->copy()->map((_, v) => v.value)->join('')
+ else
+ label = hint.label
+ endif
+
+ # add a space before or after the label
+ var padLeft: bool = hint->get('paddingLeft', false)
+ var padRight: bool = hint->get('paddingRight', false)
+ if padLeft
+ label = $' {label}'
+ endif
+ if padRight
+ label = $'{label} '
+ endif
+
+ var kind = hint->has_key('kind') ? hint.kind->string() : '1'
+ try
+ lspserver.decodePosition(bnr, hint.position)
+ var byteIdx = util.GetLineByteFromPos(bnr, hint.position)
+ if kind == "'type'" || kind == '1'
+ prop_add(hint.position.line + 1, byteIdx + 1,
+ {type: 'LspInlayHintsType', text: label, bufnr: bnr})
+ elseif kind == "'parameter'" || kind == '2'
+ prop_add(hint.position.line + 1, byteIdx + 1,
+ {type: 'LspInlayHintsParam', text: label, bufnr: bnr})
+ endif
+ catch /E966\|E964/ # Invalid lnum | Invalid col
+ # Inlay hints replies arrive asynchronously and the document might have
+ # been modified in the mean time. As the reply is stale, ignore invalid
+ # line number and column number errors.
+ endtry
+ endfor
+enddef
+
+# Timer callback to display the inlay hints.
+def InlayHintsTimerCb(lspserver: dict<any>, bnr: number, timerid: number)
+ lspserver.inlayHintsShow(bnr)
+ setbufvar(bnr, 'LspInlayHintsNeedsUpdate', false)
+enddef
+
+# Update all the inlay hints. A timer is used to throttle the updates.
+def LspInlayHintsUpdate(bnr: number)
+ if !bnr->getbufvar('LspInlayHintsNeedsUpdate', true)
+ return
+ endif
+
+ var timerid = bnr->getbufvar('LspInlayHintsTimer', -1)
+ if timerid != -1
+ timerid->timer_stop()
+ setbufvar(bnr, 'LspInlayHintsTimer', -1)
+ endif
+
+ var lspserver: dict<any> = buf.BufLspServerGet(bnr, 'inlayHint')
+ if lspserver->empty()
+ return
+ endif
+
+ if get(g:, 'LSPTest')
+ # When running tests, update the inlay hints immediately
+ InlayHintsTimerCb(lspserver, bnr, -1)
+ else
+ timerid = timer_start(300, function('InlayHintsTimerCb', [lspserver, bnr]))
+ setbufvar(bnr, 'LspInlayHintsTimer', timerid)
+ endif
+enddef
+
+# Text is modified. Need to update the inlay hints.
+def LspInlayHintsChanged(bnr: number)
+ setbufvar(bnr, 'LspInlayHintsNeedsUpdate', true)
+enddef
+
+# Trigger an update of the inlay hints in the current buffer.
+export def LspInlayHintsUpdateNow(bnr: number)
+ setbufvar(bnr, 'LspInlayHintsNeedsUpdate', true)
+ LspInlayHintsUpdate(bnr)
+enddef
+
+# Stop updating the inlay hints.
+def LspInlayHintsUpdateStop(bnr: number)
+ var timerid = bnr->getbufvar('LspInlayHintsTimer', -1)
+ if timerid != -1
+ timerid->timer_stop()
+ setbufvar(bnr, 'LspInlayHintsTimer', -1)
+ endif
+enddef
+
+# Do buffer-local initialization for displaying inlay hints
+export def BufferInit(lspserver: dict<any>, bnr: number)
+ if !lspserver.isInlayHintProvider && !lspserver.isClangdInlayHintsProvider
+ # no support for inlay hints
+ return
+ endif
+
+ # Inlays hints are disabled
+ if !opt.lspOptions.showInlayHints
+ || !lspserver.featureEnabled('inlayHint')
+ return
+ endif
+
+ var acmds: list<dict<any>> = []
+
+ # Update the inlay hints (if needed) when the cursor is not moved for some
+ # time.
+ acmds->add({bufnr: bnr,
+ event: ['CursorHold'],
+ group: 'LspInlayHints',
+ cmd: $'LspInlayHintsUpdate({bnr})'})
+ # After the text in the current buffer is modified, the inlay hints need to
+ # be updated.
+ acmds->add({bufnr: bnr,
+ event: ['TextChanged'],
+ group: 'LspInlayHints',
+ cmd: $'LspInlayHintsChanged({bnr})'})
+ # Editing a file should trigger an inlay hint update.
+ acmds->add({bufnr: bnr,
+ event: ['BufReadPost'],
+ group: 'LspInlayHints',
+ cmd: $'LspInlayHintsUpdateNow({bnr})'})
+ # Inlay hints need not be updated if a buffer is no longer active.
+ acmds->add({bufnr: bnr,
+ event: ['BufLeave'],
+ group: 'LspInlayHints',
+ cmd: $'LspInlayHintsUpdateStop({bnr})'})
+
+ # Inlay hints maybe a bit delayed if it was a sync init lsp server.
+ if lspserver.syncInit
+ acmds->add({bufnr: bnr,
+ event: ['User'],
+ group: 'LspAttached',
+ cmd: $'LspInlayHintsUpdateNow({bnr})'})
+ endif
+
+ autocmd_add(acmds)
+enddef
+
+# Track the current inlay hints enabled/disabled state. Used when the
+# "showInlayHints" option value is changed.
+var save_showInlayHints = opt.lspOptions.showInlayHints
+
+# Enable inlay hints. For all the buffers with an attached language server
+# that supports inlay hints, refresh the inlay hints.
+export def InlayHintsEnable()
+ opt.lspOptions.showInlayHints = true
+ for binfo in getbufinfo()
+ var lspservers: list<dict<any>> = buf.BufLspServersGet(binfo.bufnr)
+ if lspservers->empty()
+ continue
+ endif
+ for lspserver in lspservers
+ if !lspserver.ready
+ || !lspserver.featureEnabled('inlayHint')
+ || (!lspserver.isInlayHintProvider &&
+ !lspserver.isClangdInlayHintsProvider)
+ continue
+ endif
+ BufferInit(lspserver, binfo.bufnr)
+ LspInlayHintsUpdateNow(binfo.bufnr)
+ endfor
+ endfor
+ save_showInlayHints = true
+enddef
+
+# Disable inlay hints for the current Vim session. Clear the inlay hints in
+# all the buffers.
+export def InlayHintsDisable()
+ opt.lspOptions.showInlayHints = false
+ for binfo in getbufinfo()
+ var lspserver: dict<any> = buf.BufLspServerGet(binfo.bufnr, 'inlayHint')
+ if lspserver->empty()
+ continue
+ endif
+ LspInlayHintsUpdateStop(binfo.bufnr)
+ :silent! autocmd_delete([{bufnr: binfo.bufnr, group: 'LspInlayHints'}])
+ InlayHintsClear(binfo.bufnr)
+ endfor
+ save_showInlayHints = false
+enddef
+
+# Some options are changed. If 'showInlayHints' option is changed, then
+# either enable or disable inlay hints.
+export def LspInlayHintsOptionsChanged()
+ if save_showInlayHints && !opt.lspOptions.showInlayHints
+ InlayHintsDisable()
+ elseif !save_showInlayHints && opt.lspOptions.showInlayHints
+ InlayHintsEnable()
+ endif
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/lsp.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/lsp.vim
new file mode 100644
index 0000000..507eb00
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/lsp.vim
@@ -0,0 +1,1304 @@
+vim9script
+
+# Vim9 LSP client
+#
+# The functions called by plugin/lsp.vim are in this file.
+
+# Needs Vim 9.0 and higher
+if v:version < 900
+ finish
+endif
+
+import './options.vim' as opt
+import './lspserver.vim' as lserver
+import './util.vim'
+import './buffer.vim' as buf
+import './completion.vim'
+import './textedit.vim'
+import './diag.vim'
+import './symbol.vim'
+import './outline.vim'
+import './signature.vim'
+import './codeaction.vim'
+import './inlayhints.vim'
+import './semantichighlight.vim'
+
+# LSP server information
+var LSPServers: list<dict<any>> = []
+
+# filetype to LSP server map
+var ftypeServerMap: dict<list<dict<any>>> = {}
+
+var lspInitializedOnce = false
+
+def LspInitOnce()
+ hlset([
+ {name: 'LspTextRef', default: true, linksto: 'Search'},
+ {name: 'LspReadRef', default: true, linksto: 'DiffChange'},
+ {name: 'LspWriteRef', default: true, linksto: 'DiffDelete'}
+ ])
+
+ var override = &cursorline
+ && &cursorlineopt =~ '\<line\>\|\<screenline\>\|\<both\>'
+
+ prop_type_add('LspTextRef', {highlight: 'LspTextRef', override: override})
+ prop_type_add('LspReadRef', {highlight: 'LspReadRef', override: override})
+ prop_type_add('LspWriteRef', {highlight: 'LspWriteRef', override: override})
+
+ diag.InitOnce()
+ inlayhints.InitOnce()
+ signature.InitOnce()
+ symbol.InitOnce()
+ semantichighlight.InitOnce()
+
+ lspInitializedOnce = true
+enddef
+
+# Returns the LSP servers for the a specific filetype. Based on how well there
+# score, LSP servers with the same score are being returned.
+# Returns an empty list if the servers is not found.
+def LspGetServers(bnr: number, ftype: string): list<dict<any>>
+ if !ftypeServerMap->has_key(ftype)
+ return []
+ endif
+
+ var bufDir = bnr->bufname()->fnamemodify(':p:h')
+
+ return ftypeServerMap[ftype]->filter((key, lspserver) => {
+ # Don't run the server if no path is found
+ if !lspserver.runIfSearchFiles->empty()
+ var path = util.FindNearestRootDir(bufDir, lspserver.runIfSearchFiles)
+
+ if path->empty()
+ return false
+ endif
+ endif
+
+ # Don't run the server if a path is found
+ if !lspserver.runUnlessSearchFiles->empty()
+ var path = util.FindNearestRootDir(bufDir, lspserver.runUnlessSearchFiles)
+
+ if !path->empty()
+ return false
+ endif
+ endif
+
+ # Run the server
+ return true
+ })
+enddef
+
+# Add a LSP server for a filetype
+def LspAddServer(ftype: string, lspsrv: dict<any>)
+ var lspsrvlst = ftypeServerMap->has_key(ftype) ? ftypeServerMap[ftype] : []
+ lspsrvlst->add(lspsrv)
+ ftypeServerMap[ftype] = lspsrvlst
+enddef
+
+# Enable/disable the logging of the language server protocol messages
+def ServerDebug(arg: string)
+ if ['errors', 'messages', 'off', 'on']->index(arg) == -1
+ util.ErrMsg($'Unsupported argument "{arg}"')
+ return
+ endif
+
+ var lspservers: list<dict<any>> = buf.CurbufGetServers()
+ if lspservers->empty()
+ util.WarnMsg($'No Lsp servers found for "{@%}"')
+ return
+ endif
+
+ for lspserver in lspservers
+ if arg == 'on'
+ util.ClearTraceLogs(lspserver.logfile)
+ util.ClearTraceLogs(lspserver.errfile)
+ lspserver.debug = true
+ elseif arg == 'off'
+ lspserver.debug = false
+ elseif arg == 'messages'
+ util.ServerMessagesShow(lspserver.logfile)
+ else
+ util.ServerMessagesShow(lspserver.errfile)
+ endif
+ endfor
+enddef
+
+# Show information about all the LSP servers
+export def ShowAllServers()
+ var lines = []
+ # Add filetype to server mapping information
+ lines->add('Filetype Information')
+ lines->add('====================')
+ for [ftype, lspservers] in ftypeServerMap->items()
+ for lspserver in lspservers
+ lines->add($"Filetype: '{ftype}'")
+ lines->add($"Server Name: '{lspserver.name}'")
+ lines->add($"Server Path: '{lspserver.path}'")
+ lines->add($"Status: {lspserver.running ? 'Running' : 'Not running'}")
+ lines->add('')
+ endfor
+ endfor
+
+ # Add buffer to server mapping information
+ lines->add('Buffer Information')
+ lines->add('==================')
+ for bnr in range(1, bufnr('$'))
+ var lspservers: list<dict<any>> = buf.BufLspServersGet(bnr)
+ if !lspservers->empty()
+ lines->add($"Buffer: '{bufname(bnr)}'")
+ for lspserver in lspservers
+ lines->add($"Server Path: '{lspserver.path}'")
+ lines->add($"Status: {lspserver.running ? 'Running' : 'Not running'}")
+ endfor
+ lines->add('')
+ endif
+ endfor
+
+ var wid = bufwinid('Language-Servers')
+ if wid != -1
+ wid->win_gotoid()
+ :setlocal modifiable
+ :silent! :%d _
+ else
+ :new Language-Servers
+ :setlocal buftype=nofile
+ :setlocal bufhidden=wipe
+ :setlocal noswapfile
+ :setlocal nonumber nornu
+ :setlocal fdc=0 signcolumn=no
+ endif
+ setline(1, lines)
+ :setlocal nomodified
+ :setlocal nomodifiable
+enddef
+
+# Create a new window containing the buffer "bname" or if the window is
+# already present then jump to it.
+def OpenScratchWindow(bname: string)
+ var wid = bufwinid(bname)
+ if wid != -1
+ wid->win_gotoid()
+ :setlocal modifiable
+ :silent! :%d _
+ else
+ exe $':new {bname}'
+ :setlocal buftype=nofile
+ :setlocal bufhidden=wipe
+ :setlocal noswapfile
+ :setlocal nonumber nornu
+ :setlocal fdc=0 signcolumn=no
+ endif
+enddef
+
+# Show the status of the LSP server for the current buffer
+def ShowServer(arg: string)
+ if ['status', 'capabilities', 'initializeRequest', 'messages']->index(arg) == -1
+ util.ErrMsg($'Unsupported argument "{arg}"')
+ return
+ endif
+
+ var lspservers: list<dict<any>> = buf.CurbufGetServers()
+ if lspservers->empty()
+ util.WarnMsg($'No Lsp servers found for "{@%}"')
+ return
+ endif
+
+ var windowName: string = ''
+ var lines: list<string> = []
+ if arg->empty() || arg == 'status'
+ windowName = $'LangServer-Status'
+ for lspserver in lspservers
+ if !lines->empty()
+ lines->extend(['', repeat('=', &columns), ''])
+ endif
+ var msg = $"LSP server '{lspserver.name}' is "
+ if lspserver.running
+ msg ..= 'running'
+ else
+ msg ..= 'not running'
+ endif
+ lines->add(msg)
+ endfor
+ elseif arg == 'capabilities'
+ windowName = $'LangServer-Capabilities'
+ for lspserver in lspservers
+ if !lines->empty()
+ lines->extend(['', repeat('=', &columns), ''])
+ endif
+ lines->extend(lspserver.getCapabilities())
+ endfor
+ elseif arg == 'initializeRequest'
+ windowName = $'LangServer-InitializeRequest'
+ for lspserver in lspservers
+ if !lines->empty()
+ lines->extend(['', repeat('=', &columns), ''])
+ endif
+ lines->extend(lspserver.getInitializeRequest())
+ endfor
+ elseif arg == 'messages'
+ windowName = $'LangServer-Messages'
+ for lspserver in lspservers
+ if !lines->empty()
+ lines->extend(['', repeat('=', &columns), ''])
+ endif
+ lines->extend(lspserver.getMessages())
+ endfor
+ else
+ util.ErrMsg($'Unsupported argument "{arg}"')
+ return
+ endif
+
+ if lines->len() > 1
+ OpenScratchWindow(windowName)
+ setline(1, lines)
+ :setlocal nomodified
+ :setlocal nomodifiable
+ else
+ util.InfoMsg(lines[0])
+ endif
+enddef
+
+# Get LSP server running status for filetype "ftype"
+# Return true if running, or false if not found or not running
+export def ServerRunning(ftype: string): bool
+ if ftypeServerMap->has_key(ftype)
+ var lspservers = ftypeServerMap[ftype]
+ for lspserver in lspservers
+ if lspserver.running
+ return true
+ endif
+ endfor
+ endif
+
+ return false
+enddef
+
+# Go to a definition using "textDocument/definition" LSP request
+export def GotoDefinition(peek: bool, cmdmods: string, count: number)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('definition')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.gotoDefinition(peek, cmdmods, count)
+enddef
+
+# Go to a declaration using "textDocument/declaration" LSP request
+export def GotoDeclaration(peek: bool, cmdmods: string, count: number)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('declaration')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.gotoDeclaration(peek, cmdmods, count)
+enddef
+
+# Go to a type definition using "textDocument/typeDefinition" LSP request
+export def GotoTypedef(peek: bool, cmdmods: string, count: number)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('typeDefinition')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.gotoTypeDef(peek, cmdmods, count)
+enddef
+
+# Go to a implementation using "textDocument/implementation" LSP request
+export def GotoImplementation(peek: bool, cmdmods: string, count: number)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('implementation')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.gotoImplementation(peek, cmdmods, count)
+enddef
+
+# Switch source header using "textDocument/switchSourceHeader" LSP request
+# (Clangd specifc extension)
+export def SwitchSourceHeader()
+ var lspserver: dict<any> = buf.CurbufGetServerChecked()
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.switchSourceHeader()
+enddef
+
+# A buffer is saved. Send the "textDocument/didSave" LSP notification
+def LspSavedFile(bnr: number)
+ var lspservers: list<dict<any>> = buf.BufLspServersGet(bnr)->filter(
+ (key, lspsrv) => !lspsrv->empty() && lspsrv.running
+ )
+
+ if lspservers->empty()
+ return
+ endif
+
+ for lspserver in lspservers
+ lspserver.didSaveFile(bnr)
+ endfor
+enddef
+
+# Called after leaving insert mode. Used to process diag messages (if any)
+def LspLeftInsertMode(bnr: number)
+ var updatePending: bool = bnr->getbufvar('LspDiagsUpdatePending', false)
+ if !updatePending
+ return
+ endif
+ setbufvar(bnr, 'LspDiagsUpdatePending', false)
+
+ diag.ProcessNewDiags(bnr)
+enddef
+
+# Add buffer-local autocmds when attaching a LSP server to a buffer
+def AddBufLocalAutocmds(lspserver: dict<any>, bnr: number): void
+ var acmds: list<dict<any>> = []
+
+ # file saved notification handler
+ acmds->add({bufnr: bnr,
+ event: 'BufWritePost',
+ group: 'LSPBufferAutocmds',
+ cmd: $'LspSavedFile({bnr})'})
+
+ # Update the diagnostics when insert mode is stopped
+ acmds->add({bufnr: bnr,
+ event: 'InsertLeave',
+ group: 'LSPBufferAutocmds',
+ cmd: $'LspLeftInsertMode({bnr})'})
+
+ # Auto highlight all the occurrences of the current keyword
+ if opt.lspOptions.autoHighlight &&
+ lspserver.isDocumentHighlightProvider
+ acmds->add({bufnr: bnr,
+ event: 'CursorMoved',
+ group: 'LSPBufferAutocmds',
+ cmd: $'call LspDocHighlightClear({bnr}) | call LspDocHighlight({bnr}, "silent")'})
+ endif
+
+ autocmd_add(acmds)
+enddef
+
+# The LSP server with ID "lspserverId" is ready, initialize the LSP features
+# for buffer "bnr".
+def BufferInit(lspserverId: number, bnr: number): void
+ var lspserver = buf.BufLspServerGetById(bnr, lspserverId)
+ if lspserver->empty() || !lspserver.running
+ return
+ endif
+
+ var ftype: string = bnr->getbufvar('&filetype')
+ lspserver.textdocDidOpen(bnr, ftype)
+
+ # add a listener to track changes to this buffer
+ listener_add((_bnr: number, start: number, end: number, added: number, changes: list<dict<number>>) => {
+ lspserver.textdocDidChange(bnr, start, end, added, changes)
+ }, bnr)
+
+ AddBufLocalAutocmds(lspserver, bnr)
+
+ diag.BufferInit(lspserver, bnr)
+
+ var allServersReady = true
+ var lspservers: list<dict<any>> = buf.BufLspServersGet(bnr)
+ for lspsrv in lspservers
+ if !lspsrv.ready
+ allServersReady = false
+ break
+ endif
+ endfor
+
+ if allServersReady
+ for lspsrv in lspservers
+ # It's only possible to initialize the features when the server
+ # capabilities of all the registered language servers for this file type
+ # are known.
+ var completionServer = buf.BufLspServerGet(bnr, 'completion')
+ if !completionServer->empty() && lspsrv.id == completionServer.id
+ completion.BufferInit(lspsrv, bnr, ftype)
+ endif
+
+ var signatureServer = buf.BufLspServerGet(bnr, 'signatureHelp')
+ if !signatureServer->empty() && lspsrv.id == signatureServer.id
+ signature.BufferInit(lspsrv)
+ endif
+
+ var inlayHintServer = buf.BufLspServerGet(bnr, 'inlayHint')
+ if !inlayHintServer->empty() && lspsrv.id == inlayHintServer.id
+ inlayhints.BufferInit(lspsrv, bnr)
+ endif
+
+ var semanticServer = buf.BufLspServerGet(bnr, 'semanticTokens')
+ if !semanticServer->empty() && lspsrv.id == semanticServer.id
+ semantichighlight.BufferInit(lspserver, bnr)
+ endif
+ endfor
+
+ if exists('#User#LspAttached')
+ doautocmd <nomodeline> User LspAttached
+ endif
+ endif
+enddef
+
+# A new buffer is opened. If LSP is supported for this buffer, then add it
+export def AddFile(bnr: number): void
+ if buf.BufHasLspServer(bnr)
+ # LSP server for this buffer is already initialized and running
+ return
+ endif
+
+ # Skip remote files
+ if util.LspUriRemote(bnr->bufname()->fnamemodify(':p'))
+ return
+ endif
+
+ var ftype: string = bnr->getbufvar('&filetype')
+ if ftype->empty()
+ return
+ endif
+ var lspservers: list<dict<any>> = LspGetServers(bnr, ftype)
+ if lspservers->empty()
+ return
+ endif
+ for lspserver in lspservers
+ if !lspserver.running
+ if !lspInitializedOnce
+ LspInitOnce()
+ endif
+ lspserver.startServer(bnr)
+ endif
+ buf.BufLspServerSet(bnr, lspserver)
+
+ if lspserver.ready
+ BufferInit(lspserver.id, bnr)
+ else
+ augroup LSPBufferAutocmds
+ exe $'autocmd User LspServerReady_{lspserver.id} ++once BufferInit({lspserver.id}, {bnr})'
+ augroup END
+ endif
+ endfor
+enddef
+
+# Notify LSP server to remove a file
+export def RemoveFile(bnr: number): void
+ var lspservers: list<dict<any>> = buf.BufLspServersGet(bnr)
+ for lspserver in lspservers->copy()
+ if lspserver->empty()
+ continue
+ endif
+ if lspserver.running
+ lspserver.textdocDidClose(bnr)
+ endif
+ diag.DiagRemoveFile(bnr)
+ buf.BufLspServerRemove(bnr, lspserver)
+ endfor
+enddef
+
+# Buffer 'bnr' is loaded in a window, send the latest buffer contents to the
+# language servers.
+export def BufferLoadedInWin(bnr: number)
+ var lspservers: list<dict<any>> = buf.BufLspServersGet(bnr)
+ if lspservers->empty()
+ # No language servers for this buffer
+ return
+ endif
+ for lspserver in lspservers
+ if !lspserver->empty() && lspserver.ready
+ lspserver.textdocDidChange(bnr, 0, 0, 0, [])
+ endif
+ endfor
+ # Refresh the displayed diags visuals
+ if opt.lspOptions.autoHighlightDiags
+ diag.DiagsRefresh(bnr)
+ endif
+
+ completion.BufferLoadedInWin(bnr)
+
+ # Refresh the semantic highlights
+ if opt.lspOptions.semanticHighlight
+ var semanticServer = buf.BufLspServerGet(bnr, 'semanticTokens')
+ if !semanticServer->empty()
+ semanticServer.semanticHighlightUpdate(bnr)
+ endif
+ endif
+enddef
+
+# Stop all the LSP servers
+export def StopAllServers()
+ for lspserver in LSPServers
+ if lspserver.running
+ lspserver.stopServer()
+ endif
+ endfor
+enddef
+
+# Add all the buffers with 'filetype' set to "ftype" to the language server.
+def AddBuffersToLsp(ftype: string)
+ # Add all the buffers with the same file type as the current buffer
+ for binfo in getbufinfo({bufloaded: 1})
+ if binfo.bufnr->getbufvar('&filetype') == ftype
+ AddFile(binfo.bufnr)
+ endif
+ endfor
+enddef
+
+# Restart the LSP server for the current buffer
+def RestartServer()
+ var lspservers: list<dict<any>> = buf.CurbufGetServers()
+ if lspservers->empty()
+ util.WarnMsg($'No Lsp servers found for "{@%}"')
+ return
+ endif
+
+ # Remove all the buffers with the same file type as the current buffer
+ var ftype: string = &filetype
+ for binfo in getbufinfo()
+ if binfo.bufnr->getbufvar('&filetype') == ftype
+ RemoveFile(binfo.bufnr)
+ endif
+ endfor
+
+ for lspserver in lspservers
+ # Stop the server (if running)
+ if lspserver.running
+ lspserver.stopServer()
+ endif
+
+ # Start the server again
+ lspserver.startServer(bufnr())
+ endfor
+
+ AddBuffersToLsp(ftype)
+enddef
+
+# Add the LSP server for files with 'filetype' as "ftype".
+def AddServerForFiltype(lspserver: dict<any>, ftype: string, omnicompl: bool)
+ LspAddServer(ftype, lspserver)
+ completion.OmniComplSet(ftype, omnicompl)
+
+ # If a buffer of this file type is already present, then send it to the LSP
+ # server now.
+ AddBuffersToLsp(ftype)
+enddef
+
+# Register a LSP server for one or more file types
+export def AddServer(serverList: list<dict<any>>)
+ for server in serverList
+ if !server->has_key('filetype') || !server->has_key('path')
+ util.ErrMsg('LSP server information is missing filetype or path')
+ continue
+ endif
+ # Enable omni-completion by default
+ var omnicompl_def: bool = false
+ if opt.lspOptions.omniComplete == true
+ || (opt.lspOptions.omniComplete == null
+ && !opt.lspOptions.autoComplete)
+ omnicompl_def = true
+ endif
+ server.omnicompl = server->get('omnicompl', omnicompl_def)
+
+ if !server.path->executable()
+ if !opt.lspOptions.ignoreMissingServer
+ util.ErrMsg($'LSP server {server.path} is not found')
+ endif
+ continue
+ endif
+ if server->has_key('args')
+ if server.args->type() != v:t_list
+ util.ErrMsg($'Arguments for LSP server {server.args} is not a List')
+ continue
+ endif
+ else
+ server.args = []
+ endif
+
+ if !server->has_key('initializationOptions')
+ || server.initializationOptions->type() != v:t_dict
+ server.initializationOptions = {}
+ endif
+
+ if !server->has_key('forceOffsetEncoding')
+ || server.forceOffsetEncoding->type() != v:t_string
+ || (server.forceOffsetEncoding != 'utf-8'
+ && server.forceOffsetEncoding != 'utf-16'
+ && server.forceOffsetEncoding != 'utf-32')
+ server.forceOffsetEncoding = ''
+ endif
+
+ if !server->has_key('customNotificationHandlers')
+ || server.customNotificationHandlers->type() != v:t_dict
+ server.customNotificationHandlers = {}
+ endif
+
+ if server->has_key('processDiagHandler')
+ if server.processDiagHandler->type() != v:t_func
+ util.ErrMsg($'Setting of processDiagHandler {server.processDiagHandler} is not a Funcref nor lambda')
+ continue
+ endif
+ else
+ server.processDiagHandler = null_function
+ endif
+
+ if !server->has_key('customRequestHandlers')
+ || server.customRequestHandlers->type() != v:t_dict
+ server.customRequestHandlers = {}
+ endif
+
+ if !server->has_key('features') || server.features->type() != v:t_dict
+ server.features = {}
+ endif
+
+ if server.omnicompl->type() != v:t_bool
+ util.ErrMsg($'Setting of omnicompl {server.omnicompl} is not a Boolean')
+ continue
+ endif
+
+ if !server->has_key('syncInit')
+ server.syncInit = false
+ endif
+
+ if !server->has_key('name') || server.name->type() != v:t_string
+ || server.name->empty()
+ # Use the executable name (without the extension) as the language server
+ # name.
+ server.name = server.path->fnamemodify(':t:r')
+ endif
+
+ if !server->has_key('debug') || server.debug->type() != v:t_bool
+ server.debug = false
+ endif
+
+ if !server->has_key('traceLevel')
+ || server->type() != v:t_string
+ || (server.traceLevel != 'off' && server.traceLevel != 'debug'
+ && server.traceLevel != 'verbose')
+ server.traceLevel = 'off'
+ endif
+
+ if !server->has_key('workspaceConfig')
+ || server.workspaceConfig->type() != v:t_dict
+ server.workspaceConfig = {}
+ endif
+
+ if !server->has_key('rootSearch') || server.rootSearch->type() != v:t_list
+ server.rootSearch = []
+ endif
+
+ if !server->has_key('runIfSearch') ||
+ server.runIfSearch->type() != v:t_list
+ server.runIfSearch = []
+ endif
+
+ if !server->has_key('runUnlessSearch') ||
+ server.runUnlessSearch->type() != v:t_list
+ server.runUnlessSearch = []
+ endif
+
+ var lspserver: dict<any> = lserver.NewLspServer(server)
+
+ var ftypes = server.filetype
+ if ftypes->type() == v:t_string
+ AddServerForFiltype(lspserver, ftypes, server.omnicompl)
+ elseif ftypes->type() == v:t_list
+ for ftype in ftypes
+ AddServerForFiltype(lspserver, ftype, server.omnicompl)
+ endfor
+ else
+ util.ErrMsg($'Unsupported file type information "{ftypes->string()}" in LSP server registration')
+ continue
+ endif
+ endfor
+enddef
+
+# The LSP server is considered ready when the server capabilities are
+# received ("initialize" LSP reply message)
+export def ServerReady(): bool
+ var fname: string = @%
+ if fname->empty()
+ return false
+ endif
+
+ var lspservers: list<dict<any>> = buf.CurbufGetServers()
+ if lspservers->empty()
+ return false
+ endif
+
+ for lspserver in lspservers
+ if !lspserver.ready
+ return false
+ endif
+ endfor
+
+ return true
+enddef
+
+# set the LSP server trace level for the current buffer
+# Params: SetTraceParams
+def ServerTraceSet(traceVal: string)
+ if ['off', 'messages', 'verbose']->index(traceVal) == -1
+ util.ErrMsg($'Unsupported argument "{traceVal}"')
+ return
+ endif
+
+ var lspservers: list<dict<any>> = buf.CurbufGetServers()
+ if lspservers->empty()
+ util.WarnMsg($'No Lsp servers found for "{@%}"')
+ return
+ endif
+
+ for lspserver in lspservers
+ lspserver.setTrace(traceVal)
+ endfor
+enddef
+
+# Display the diagnostic messages from the LSP server for the current buffer
+# in a quickfix list
+export def ShowDiagnostics(): void
+ diag.ShowAllDiags()
+enddef
+
+# Show the diagnostic message for the current line
+export def LspShowCurrentDiag(atPos: bool)
+ diag.ShowCurrentDiag(atPos)
+enddef
+
+# get the count of diagnostics in the current buffer
+export def ErrorCount(): dict<number>
+ var res = {Error: 0, Warn: 0, Info: 0, Hint: 0}
+ var fname: string = @%
+ if fname->empty()
+ return res
+ endif
+
+ return diag.DiagsGetErrorCount(bufnr())
+enddef
+
+# jump to the next/previous/first diagnostic message in the current buffer
+export def JumpToDiag(which: string, count: number = 0): void
+ diag.LspDiagsJump(which, count)
+enddef
+
+# Display the hover message from the LSP server for the current cursor
+# location
+export def Hover(cmdmods: string)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('hover')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.hover(cmdmods)
+enddef
+
+# Enable or disable inlay hints
+export def InlayHints(ctl: string)
+ if ctl == 'enable'
+ inlayhints.InlayHintsEnable()
+ elseif ctl == 'disable'
+ inlayhints.InlayHintsDisable()
+ else
+ util.ErrMsg($'LspInlayHints - Unsupported argument "{ctl}"')
+ endif
+enddef
+
+# Command-line completion for the ":LspInlayHints" command
+export def LspInlayHintsComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
+ var l = ['enable', 'disable']
+ return filter(l, (_, val) => val =~ $'^{arglead}')
+enddef
+
+# show symbol references
+export def ShowReferences(peek: bool)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('references')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.showReferences(peek)
+enddef
+
+# highlight all the places where a symbol is referenced
+def g:LspDocHighlight(bnr: number = bufnr(), cmdmods: string = '')
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('documentHighlight')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.docHighlight(bnr, cmdmods)
+enddef
+
+# clear the symbol reference highlight
+def g:LspDocHighlightClear(bnr: number = bufnr())
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('documentHighlight')
+ if lspserver->empty()
+ return
+ endif
+
+ var propNames = ['LspTextRef', 'LspReadRef', 'LspWriteRef']
+ if has('patch-9.0.0233')
+ prop_remove({types: propNames, bufnr: bnr, all: true})
+ else
+ for propName in propNames
+ prop_remove({type: propName, bufnr: bnr, all: true})
+ endfor
+ endif
+enddef
+
+def g:LspRequestDocSymbols()
+ if outline.SkipOutlineRefresh()
+ return
+ endif
+
+ var fname: string = @%
+ if fname->empty()
+ return
+ endif
+
+ var lspserver: dict<any> = buf.CurbufGetServer('documentSymbol')
+ if lspserver->empty() || !lspserver.running || !lspserver.ready
+ return
+ endif
+
+ lspserver.getDocSymbols(fname, true)
+enddef
+
+# open a window and display all the symbols in a file (outline)
+export def Outline(cmdmods: string, winsize: number)
+ var fname: string = @%
+ if fname->empty()
+ return
+ endif
+
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('documentSymbol')
+ if lspserver->empty() || !lspserver.running || !lspserver.ready
+ return
+ endif
+
+ outline.OpenOutlineWindow(cmdmods, winsize)
+ g:LspRequestDocSymbols()
+enddef
+
+# show all the symbols in a file in a popup menu
+export def ShowDocSymbols()
+ var fname: string = @%
+ if fname->empty()
+ return
+ endif
+
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('documentSymbol')
+ if lspserver->empty() || !lspserver.running || !lspserver.ready
+ return
+ endif
+
+ lspserver.getDocSymbols(fname, false)
+enddef
+
+# Format the entire file
+export def TextDocFormat(range_args: number, line1: number, line2: number)
+ if !&modifiable
+ util.ErrMsg('Current file is not a modifiable file')
+ return
+ endif
+
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('documentFormatting')
+ if lspserver->empty()
+ return
+ endif
+
+ var fname: string = @%
+ if range_args > 0
+ lspserver.textDocFormat(fname, true, line1, line2)
+ else
+ lspserver.textDocFormat(fname, false, 0, 0)
+ endif
+enddef
+
+# TODO: Add support for textDocument.onTypeFormatting?
+# Will this slow down Vim?
+
+# Display all the locations where the current symbol is called from.
+# Uses LSP "callHierarchy/incomingCalls" request
+export def IncomingCalls()
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('callHierarchy')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.incomingCalls(@%)
+enddef
+
+# Display all the symbols used by the current symbol.
+# Uses LSP "callHierarchy/outgoingCalls" request
+export def OutgoingCalls()
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('callHierarchy')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.outgoingCalls(@%)
+enddef
+
+# Display the type hierarchy for the current symbol. Direction is 0 for
+# sub types and 1 for super types.
+export def TypeHierarchy(direction: number)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('typeHierarchy')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.typeHierarchy(direction)
+enddef
+
+# Rename a symbol
+# Uses LSP "textDocument/rename" request
+export def Rename(a_newName: string)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('rename')
+ if lspserver->empty()
+ return
+ endif
+
+ var newName: string = a_newName
+ if newName->empty()
+ var sym: string = expand('<cword>')
+ newName = input($"Rename symbol '{sym}' to: ", sym)
+ if newName->empty()
+ return
+ endif
+
+ # clear the input prompt
+ :echo "\r"
+ endif
+
+ lspserver.renameSymbol(newName)
+enddef
+
+# Perform a code action
+# Uses LSP "textDocument/codeAction" request
+export def CodeAction(line1: number, line2: number, query: string)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('codeAction')
+ if lspserver->empty()
+ return
+ endif
+
+ var fname: string = @%
+ lspserver.codeAction(fname, line1, line2, query)
+enddef
+
+# Code lens
+# Uses LSP "textDocument/codeLens" request
+export def CodeLens()
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('codeLens')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.codeLens(@%)
+enddef
+
+# Perform a workspace wide symbol lookup
+# Uses LSP "workspace/symbol" request
+export def SymbolSearch(queryArg: string, cmdmods: string)
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('workspaceSymbol')
+ if lspserver->empty()
+ return
+ endif
+
+ var query: string = queryArg
+ if query->empty()
+ query = input('Lookup symbol: ', expand('<cword>'))
+ if query->empty()
+ return
+ endif
+ endif
+ :redraw!
+
+ lspserver.workspaceQuery(query, true, cmdmods)
+enddef
+
+# Display the list of workspace folders
+export def ListWorkspaceFolders()
+ var lspservers: list<dict<any>> = buf.CurbufGetServers()
+ for lspserver in lspservers
+ util.InfoMsg($'Workspace Folders: "{lspserver.name}" {lspserver.workspaceFolders->string()}')
+ endfor
+enddef
+
+# Add a workspace folder. Default is to use the current folder.
+export def AddWorkspaceFolder(dirArg: string)
+ var dirName: string = dirArg
+ if dirName->empty()
+ dirName = input('Add Workspace Folder: ', getcwd(), 'dir')
+ if dirName->empty()
+ return
+ endif
+ endif
+ :redraw!
+ if !dirName->isdirectory()
+ util.ErrMsg($'{dirName} is not a directory')
+ return
+ endif
+
+ var lspservers: list<dict<any>> = buf.CurbufGetServers()
+
+ for lspserver in lspservers
+ lspserver.addWorkspaceFolder(dirName)
+ endfor
+enddef
+
+# Remove a workspace folder. Default is to use the current folder.
+export def RemoveWorkspaceFolder(dirArg: string)
+ var dirName: string = dirArg
+ if dirName->empty()
+ dirName = input('Remove Workspace Folder: ', getcwd(), 'dir')
+ if dirName->empty()
+ return
+ endif
+ endif
+ :redraw!
+ if !dirName->isdirectory()
+ util.ErrMsg($'{dirName} is not a directory')
+ return
+ endif
+
+ var lspservers: list<dict<any>> = buf.CurbufGetServers()
+ for lspserver in lspservers
+ lspserver.removeWorkspaceFolder(dirName)
+ endfor
+enddef
+
+# expand the previous selection or start a new selection
+export def SelectionExpand()
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('selectionRange')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.selectionExpand()
+enddef
+
+# shrink the previous selection or start a new selection
+export def SelectionShrink()
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('selectionRange')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.selectionShrink()
+enddef
+
+# fold the entire document
+export def FoldDocument()
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('foldingRange')
+ if lspserver->empty()
+ return
+ endif
+
+ if &foldmethod != 'manual'
+ util.ErrMsg("Only works when 'foldmethod' is 'manual'")
+ return
+ endif
+
+ var fname: string = @%
+ lspserver.foldRange(fname)
+enddef
+
+# Enable diagnostic highlighting for all the buffers
+export def DiagHighlightEnable()
+ diag.DiagsHighlightEnable()
+enddef
+
+# Disable diagnostic highlighting for all the buffers
+export def DiagHighlightDisable()
+ diag.DiagsHighlightDisable()
+enddef
+
+# Function to use with the 'tagfunc' option.
+export def TagFunc(pat: string, flags: string, info: dict<any>): any
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('definition')
+ if lspserver->empty()
+ return v:null
+ endif
+
+ return lspserver.tagFunc(pat, flags, info)
+enddef
+
+# Function to use with the 'formatexpr' option.
+export def FormatExpr(): number
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('documentFormatting')
+ if lspserver->empty()
+ return 1
+ endif
+
+ lspserver.textDocFormat(@%, true, v:lnum, v:lnum + v:count - 1)
+ return 0
+enddef
+
+export def RegisterCmdHandler(cmd: string, Handler: func)
+ codeaction.RegisterCmdHandler(cmd, Handler)
+enddef
+
+# Command-line completion for the ":LspServer <cmd>" and ":LspDiag <cmd>" sub
+# commands
+def LspSubCmdComplete(cmds: list<string>, arglead: string, cmdline: string, cursorPos: number): list<string>
+ var wordBegin = cmdline->match('\s\+\zs\S', cursorPos)
+ if wordBegin == -1
+ return cmds
+ endif
+
+ # Make sure there are no additional sub-commands
+ var wordEnd = cmdline->stridx(' ', wordBegin)
+ if wordEnd == -1
+ return cmds->filter((_, val) => val =~ $'^{arglead}')
+ endif
+
+ return []
+enddef
+
+# Command-line completion for the ":LspDiag highlight" command
+def LspDiagHighlightComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
+ return LspSubCmdComplete(['enable', 'disable'], arglead, cmdline, cursorPos)
+enddef
+
+# Command-line completion for the ":LspDiag" command
+export def LspDiagComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
+ var wordBegin = -1
+ var wordEnd = -1
+ var l = ['first', 'current', 'here', 'highlight', 'last', 'next', 'nextWrap',
+ 'prev', 'prevWrap', 'show']
+
+ # Skip the command name
+ var i = cmdline->stridx(' ', 0)
+ wordBegin = cmdline->match('\s\+\zs\S', i)
+ if wordBegin == -1
+ return l
+ endif
+
+ wordEnd = cmdline->stridx(' ', wordBegin)
+ if wordEnd == -1
+ return filter(l, (_, val) => val =~ $'^{arglead}')
+ endif
+
+ var cmd = cmdline->strpart(wordBegin, wordEnd - wordBegin)
+ if cmd == 'highlight'
+ return LspDiagHighlightComplete(arglead, cmdline, wordEnd)
+ endif
+
+ return []
+enddef
+
+# ":LspDiag" command handler
+export def LspDiagCmd(args: string, cmdCount: number, force: bool)
+ if args->stridx('highlight') == 0
+ if args[9] == ' '
+ var subcmd = args[10 : ]->trim()
+ if subcmd == 'enable'
+ diag.DiagsHighlightEnable()
+ elseif subcmd == 'disable'
+ diag.DiagsHighlightDisable()
+ else
+ util.ErrMsg($':LspDiag highlight - Unsupported argument "{subcmd}"')
+ endif
+ else
+ util.ErrMsg('Argument required for ":LspDiag highlight"')
+ endif
+ elseif args == 'first'
+ diag.LspDiagsJump('first', 0)
+ elseif args == 'current'
+ LspShowCurrentDiag(force)
+ elseif args == 'here'
+ diag.LspDiagsJump('here', 0)
+ elseif args == 'last'
+ diag.LspDiagsJump('last', 0)
+ elseif args == 'next'
+ diag.LspDiagsJump('next', cmdCount)
+ elseif args == 'nextWrap'
+ diag.LspDiagsJump('nextWrap', cmdCount)
+ elseif args == 'prev'
+ diag.LspDiagsJump('prev', cmdCount)
+ elseif args == 'prevWrap'
+ diag.LspDiagsJump('prevWrap', cmdCount)
+ elseif args == 'show'
+ ShowDiagnostics()
+ else
+ util.ErrMsg($':LspDiag - Unsupported argument "{args}"')
+ endif
+enddef
+
+# Command-line completion for the ":LspServer debug" command
+def LspServerDebugComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
+ return LspSubCmdComplete(['errors', 'messages', 'off', 'on'], arglead,
+ cmdline, cursorPos)
+enddef
+
+# Command-line completion for the ":LspServer show" command
+def LspServerShowComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
+ return LspSubCmdComplete(['capabilities', 'initializeRequest', 'messages',
+ 'status'], arglead, cmdline, cursorPos)
+enddef
+
+# Command-line completion for the ":LspServer trace" command
+def LspServerTraceComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
+ return LspSubCmdComplete(['messages', 'off', 'verbose'], arglead, cmdline,
+ cursorPos)
+enddef
+
+# Command-line completion for the ":LspServer" command
+export def LspServerComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
+ var wordBegin = -1
+ var wordEnd = -1
+ var l = ['debug', 'restart', 'show', 'trace']
+
+ # Skip the command name
+ var i = cmdline->stridx(' ', 0)
+ wordBegin = cmdline->match('\s\+\zs\S', i)
+ if wordBegin == -1
+ return l
+ endif
+
+ wordEnd = cmdline->stridx(' ', wordBegin)
+ if wordEnd == -1
+ return filter(l, (_, val) => val =~ $'^{arglead}')
+ endif
+
+ var cmd = cmdline->strpart(wordBegin, wordEnd - wordBegin)
+ if cmd == 'debug'
+ return LspServerDebugComplete(arglead, cmdline, wordEnd)
+ elseif cmd == 'restart'
+ elseif cmd == 'show'
+ return LspServerShowComplete(arglead, cmdline, wordEnd)
+ elseif cmd == 'trace'
+ return LspServerTraceComplete(arglead, cmdline, wordEnd)
+ endif
+
+ return []
+enddef
+
+# ":LspServer" command handler
+export def LspServerCmd(args: string)
+ if args->stridx('debug') == 0
+ if args[5] == ' '
+ var subcmd = args[6 : ]->trim()
+ ServerDebug(subcmd)
+ else
+ util.ErrMsg('Argument required for ":LspServer debug"')
+ endif
+ elseif args == 'restart'
+ RestartServer()
+ elseif args->stridx('show') == 0
+ if args[4] == ' '
+ var subcmd = args[5 : ]->trim()
+ ShowServer(subcmd)
+ else
+ util.ErrMsg('Argument required for ":LspServer show"')
+ endif
+ elseif args->stridx('trace') == 0
+ if args[5] == ' '
+ var subcmd = args[6 : ]->trim()
+ ServerTraceSet(subcmd)
+ else
+ util.ErrMsg('Argument required for ":LspServer trace"')
+ endif
+ else
+ util.ErrMsg($'LspServer - Unsupported argument "{args}"')
+ endif
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2 noexpandtab
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/lspserver.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/lspserver.vim
new file mode 100644
index 0000000..bfbdd77
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/lspserver.vim
@@ -0,0 +1,1982 @@
+vim9script
+
+# LSP server functions
+#
+# The functions to send request messages to the language server are in this
+# file.
+#
+# Refer to https://microsoft.github.io/language-server-protocol/specification
+# for the Language Server Protocol (LSP) specificaiton.
+
+import './options.vim' as opt
+import './handlers.vim'
+import './util.vim'
+import './capabilities.vim'
+import './offset.vim'
+import './diag.vim'
+import './selection.vim'
+import './symbol.vim'
+import './textedit.vim'
+import './completion.vim'
+import './hover.vim'
+import './signature.vim'
+import './codeaction.vim'
+import './codelens.vim'
+import './callhierarchy.vim' as callhier
+import './typehierarchy.vim' as typehier
+import './inlayhints.vim'
+import './semantichighlight.vim'
+
+# LSP server standard output handler
+def Output_cb(lspserver: dict<any>, chan: channel, msg: any): void
+ if lspserver.debug
+ lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Received {msg->string()}')
+ endif
+ lspserver.data = msg
+ lspserver.processMessages()
+enddef
+
+# LSP server error output handler
+def Error_cb(lspserver: dict<any>, chan: channel, emsg: string): void
+ lspserver.errorLog(emsg)
+enddef
+
+# LSP server exit callback
+def Exit_cb(lspserver: dict<any>, job: job, status: number): void
+ util.WarnMsg($'{strftime("%m/%d/%y %T")}: LSP server ({lspserver.name}) exited with status {status}')
+ lspserver.running = false
+ lspserver.ready = false
+ lspserver.requests = {}
+enddef
+
+# Start a LSP server
+#
+def StartServer(lspserver: dict<any>, bnr: number): number
+ if lspserver.running
+ util.WarnMsg($'LSP server "{lspserver.name}" is already running')
+ return 0
+ endif
+
+ var cmd = [lspserver.path]
+ cmd->extend(lspserver.args)
+
+ var opts = {in_mode: 'lsp',
+ out_mode: 'lsp',
+ err_mode: 'raw',
+ noblock: 1,
+ out_cb: function(Output_cb, [lspserver]),
+ err_cb: function(Error_cb, [lspserver]),
+ exit_cb: function(Exit_cb, [lspserver])}
+
+ lspserver.data = ''
+ lspserver.caps = {}
+ lspserver.nextID = 1
+ lspserver.requests = {}
+ lspserver.omniCompletePending = false
+ lspserver.completionLazyDoc = false
+ lspserver.completionTriggerChars = []
+ lspserver.signaturePopup = -1
+
+ var job = cmd->job_start(opts)
+ if job->job_status() == 'fail'
+ util.ErrMsg($'Failed to start LSP server {lspserver.path}')
+ return 1
+ endif
+
+ # wait a little for the LSP server to start
+ sleep 10m
+
+ lspserver.job = job
+ lspserver.running = true
+
+ lspserver.initServer(bnr)
+
+ return 0
+enddef
+
+# process the "initialize" method reply from the LSP server
+# Result: InitializeResult
+def ServerInitReply(lspserver: dict<any>, initResult: dict<any>): void
+ if initResult->empty()
+ return
+ endif
+
+ var caps: dict<any> = initResult.capabilities
+ lspserver.caps = caps
+
+ for [key, val] in initResult->items()
+ if key == 'capabilities'
+ continue
+ endif
+
+ lspserver.caps[$'~additionalInitResult_{key}'] = val
+ endfor
+
+ capabilities.ProcessServerCaps(lspserver, caps)
+
+ if caps->has_key('completionProvider')
+ lspserver.completionTriggerChars =
+ caps.completionProvider->get('triggerCharacters', [])
+ lspserver.completionLazyDoc =
+ caps.completionProvider->get('resolveProvider', false)
+ endif
+
+ # send a "initialized" notification to server
+ lspserver.sendInitializedNotif()
+ # send any workspace configuration (optional)
+ if !lspserver.workspaceConfig->empty()
+ lspserver.sendWorkspaceConfig()
+ endif
+ lspserver.ready = true
+ if exists($'#User#LspServerReady{lspserver.name}')
+ exe $'doautocmd <nomodeline> User LspServerReady{lspserver.name}'
+ endif
+ # Used internally, and shouldn't be used by users
+ if exists($'#User#LspServerReady_{lspserver.id}')
+ exe $'doautocmd <nomodeline> User LspServerReady_{lspserver.id}'
+ endif
+
+ # set the server debug trace level
+ if lspserver.traceLevel != 'off'
+ lspserver.setTrace(lspserver.traceLevel)
+ endif
+
+ # if the outline window is opened, then request the symbols for the current
+ # buffer
+ if bufwinid('LSP-Outline') != -1
+ lspserver.getDocSymbols(@%, true)
+ endif
+
+ # Update the inlay hints (if enabled)
+ if opt.lspOptions.showInlayHints && (lspserver.isInlayHintProvider
+ || lspserver.isClangdInlayHintsProvider)
+ inlayhints.LspInlayHintsUpdateNow(bufnr())
+ endif
+enddef
+
+# Request: "initialize"
+# Param: InitializeParams
+def InitServer(lspserver: dict<any>, bnr: number)
+ # interface 'InitializeParams'
+ var initparams: dict<any> = {}
+ initparams.processId = getpid()
+ initparams.clientInfo = {
+ name: 'Vim',
+ version: v:versionlong->string(),
+ }
+
+ # Compute the rootpath (based on the directory of the buffer)
+ var rootPath = ''
+ var rootSearchFiles = lspserver.rootSearchFiles
+ var bufDir = bnr->bufname()->fnamemodify(':p:h')
+ if !rootSearchFiles->empty()
+ rootPath = util.FindNearestRootDir(bufDir, rootSearchFiles)
+ endif
+ if rootPath->empty()
+ var cwd = getcwd()
+
+ # bufDir is within cwd
+ var bufDirPrefix = bufDir[0 : cwd->strcharlen() - 1]
+ if &fileignorecase
+ ? bufDirPrefix ==? cwd
+ : bufDirPrefix == cwd
+ rootPath = cwd
+ else
+ rootPath = bufDir
+ endif
+ endif
+
+ lspserver.workspaceFolders = [rootPath]
+
+ var rootUri = util.LspFileToUri(rootPath)
+ initparams.rootPath = rootPath
+ initparams.rootUri = rootUri
+ initparams.workspaceFolders = [{
+ name: rootPath->fnamemodify(':t'),
+ uri: rootUri
+ }]
+
+ initparams.trace = 'off'
+ initparams.capabilities = capabilities.GetClientCaps()
+ if !lspserver.initializationOptions->empty()
+ initparams.initializationOptions = lspserver.initializationOptions
+ else
+ initparams.initializationOptions = {}
+ endif
+
+ lspserver.rpcInitializeRequest = initparams
+
+ lspserver.rpc_a('initialize', initparams, ServerInitReply)
+enddef
+
+# Send a "initialized" notification to the language server
+def SendInitializedNotif(lspserver: dict<any>)
+ # Notification: 'initialized'
+ # Params: InitializedParams
+ lspserver.sendNotification('initialized')
+enddef
+
+# Request: shutdown
+# Param: void
+def ShutdownServer(lspserver: dict<any>): void
+ lspserver.rpc('shutdown', {})
+enddef
+
+# Send a 'exit' notification to the language server
+def ExitServer(lspserver: dict<any>): void
+ # Notification: 'exit'
+ # Params: void
+ lspserver.sendNotification('exit')
+enddef
+
+# Stop a LSP server
+def StopServer(lspserver: dict<any>): number
+ if !lspserver.running
+ util.WarnMsg($'LSP server {lspserver.name} is not running')
+ return 0
+ endif
+
+ # Send the shutdown request to the server
+ lspserver.shutdownServer()
+
+ # Notify the server to exit
+ lspserver.exitServer()
+
+ # Wait for the server to process the exit notification and exit for a
+ # maximum of 2 seconds.
+ var maxCount: number = 1000
+ while lspserver.job->job_status() == 'run' && maxCount > 0
+ sleep 2m
+ maxCount -= 1
+ endwhile
+
+ if lspserver.job->job_status() == 'run'
+ lspserver.job->job_stop()
+ endif
+ lspserver.running = false
+ lspserver.ready = false
+ lspserver.requests = {}
+ return 0
+enddef
+
+# Set the language server trace level using the '$/setTrace' notification.
+# Supported values for "traceVal" are "off", "messages" and "verbose".
+def SetTrace(lspserver: dict<any>, traceVal: string)
+ # Notification: '$/setTrace'
+ # Params: SetTraceParams
+ var params = {value: traceVal}
+ lspserver.sendNotification('$/setTrace', params)
+enddef
+
+# Log a debug message to the LSP server debug file
+def TraceLog(lspserver: dict<any>, msg: string)
+ if lspserver.debug
+ util.TraceLog(lspserver.logfile, false, msg)
+ endif
+enddef
+
+# Log an error message to the LSP server error file
+def ErrorLog(lspserver: dict<any>, errmsg: string)
+ if lspserver.debug
+ util.TraceLog(lspserver.errfile, true, errmsg)
+ endif
+enddef
+
+# Return the next id for a LSP server request message
+def NextReqID(lspserver: dict<any>): number
+ var id = lspserver.nextID
+ lspserver.nextID = id + 1
+ return id
+enddef
+
+# create a LSP server request message
+def CreateRequest(lspserver: dict<any>, method: string): dict<any>
+ var req = {
+ jsonrpc: '2.0',
+ id: lspserver.nextReqID(),
+ method: method,
+ params: {}
+ }
+
+ # Save the request, so that the corresponding response can be processed
+ lspserver.requests->extend({[req.id->string()]: req})
+
+ return req
+enddef
+
+# create a LSP server response message
+def CreateResponse(lspserver: dict<any>, req_id: number): dict<any>
+ var resp = {
+ jsonrpc: '2.0',
+ id: req_id
+ }
+ return resp
+enddef
+
+# create a LSP server notification message
+def CreateNotification(lspserver: dict<any>, notif: string): dict<any>
+ var req = {
+ jsonrpc: '2.0',
+ method: notif,
+ params: {}
+ }
+
+ return req
+enddef
+
+# send a response message to the server
+def SendResponse(lspserver: dict<any>, request: dict<any>, result: any, error: dict<any>)
+ if (request.id->type() == v:t_string
+ && (request.id->trim() =~ '[^[:digit:]]\+'
+ || request.id->trim()->empty()))
+ || (request.id->type() != v:t_string && request.id->type() != v:t_number)
+ util.ErrMsg('request.id of response to LSP server is not a correct number')
+ return
+ endif
+ var resp: dict<any> = lspserver.createResponse(
+ request.id->type() == v:t_string ? request.id->str2nr() : request.id)
+ if error->empty()
+ resp->extend({result: result})
+ else
+ resp->extend({error: error})
+ endif
+ lspserver.sendMessage(resp)
+enddef
+
+# Send a request message to LSP server
+def SendMessage(lspserver: dict<any>, content: dict<any>): void
+ var job = lspserver.job
+ if job->job_status() != 'run'
+ # LSP server has exited
+ return
+ endif
+ job->ch_sendexpr(content)
+ if content->has_key('id')
+ if lspserver.debug
+ lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Sent {content->string()}')
+ endif
+ endif
+enddef
+
+# Send a notification message to the language server
+def SendNotification(lspserver: dict<any>, method: string, params: any = {})
+ var notif: dict<any> = CreateNotification(lspserver, method)
+ notif.params->extend(params)
+ lspserver.sendMessage(notif)
+enddef
+
+# Translate an LSP error code into a readable string
+def LspGetErrorMessage(errcode: number): string
+ var errmap = {
+ -32001: 'UnknownErrorCode',
+ -32002: 'ServerNotInitialized',
+ -32600: 'InvalidRequest',
+ -32601: 'MethodNotFound',
+ -32602: 'InvalidParams',
+ -32603: 'InternalError',
+ -32700: 'ParseError',
+ -32800: 'RequestCancelled',
+ -32801: 'ContentModified',
+ -32802: 'ServerCancelled',
+ -32803: 'RequestFailed'
+ }
+
+ return errmap->get(errcode, errcode->string())
+enddef
+
+# Process a LSP server response error and display an error message.
+def ProcessLspServerError(method: string, responseError: dict<any>)
+ # request failed
+ var emsg: string = responseError.message
+ emsg ..= $', error = {LspGetErrorMessage(responseError.code)}'
+ if responseError->has_key('data')
+ emsg ..= $', data = {responseError.data->string()}'
+ endif
+ util.ErrMsg($'request {method} failed ({emsg})')
+enddef
+
+# Send a sync RPC request message to the LSP server and return the received
+# reply. In case of an error, an empty Dict is returned.
+def Rpc(lspserver: dict<any>, method: string, params: any, handleError: bool = true): dict<any>
+ var req = {
+ method: method,
+ params: params
+ }
+
+ var job = lspserver.job
+ if job->job_status() != 'run'
+ # LSP server has exited
+ return {}
+ endif
+
+ if lspserver.debug
+ lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Sent {req->string()}')
+ endif
+
+ # Do the synchronous RPC call
+ var reply = job->ch_evalexpr(req)
+
+ if lspserver.debug
+ lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Received {reply->string()}')
+ endif
+
+ if reply->has_key('result')
+ # successful reply
+ return reply
+ endif
+
+ if reply->has_key('error') && handleError
+ # request failed
+ ProcessLspServerError(method, reply.error)
+ endif
+
+ return {}
+enddef
+
+# LSP server asynchronous RPC callback
+def AsyncRpcCb(lspserver: dict<any>, method: string, RpcCb: func, chan: channel, reply: dict<any>)
+ if lspserver.debug
+ lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Received {reply->string()}')
+ endif
+
+ if reply->empty()
+ return
+ endif
+
+ if reply->has_key('error')
+ # request failed
+ ProcessLspServerError(method, reply.error)
+ return
+ endif
+
+ if !reply->has_key('result')
+ util.ErrMsg($'request {method} failed (no result)')
+ return
+ endif
+
+ RpcCb(lspserver, reply.result)
+enddef
+
+# Send a async RPC request message to the LSP server with a callback function.
+# Returns the LSP message id. This id can be used to cancel the RPC request
+# (if needed). Returns -1 on error.
+def AsyncRpc(lspserver: dict<any>, method: string, params: any, Cbfunc: func): number
+ var req = {
+ method: method,
+ params: params
+ }
+
+ var job = lspserver.job
+ if job->job_status() != 'run'
+ # LSP server has exited
+ return -1
+ endif
+
+ if lspserver.debug
+ lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Sent {req->string()}')
+ endif
+
+ # Do the asynchronous RPC call
+ var Fn = function('AsyncRpcCb', [lspserver, method, Cbfunc])
+
+ var reply: dict<any>
+ if get(g:, 'LSPTest')
+ # When running LSP tests, make this a synchronous RPC call
+ reply = Rpc(lspserver, method, params)
+ Fn(test_null_channel(), reply)
+ else
+ # Otherwise, make an asynchronous RPC call
+ reply = job->ch_sendexpr(req, {callback: Fn})
+ endif
+ if reply->empty()
+ return -1
+ endif
+
+ return reply.id
+enddef
+
+# Wait for a response message from the LSP server for the request "req"
+# Waits for a maximum of 5 seconds
+def WaitForResponse(lspserver: dict<any>, req: dict<any>)
+ var maxCount: number = 2500
+ var key: string = req.id->string()
+
+ while lspserver.requests->has_key(key) && maxCount > 0
+ sleep 2m
+ maxCount -= 1
+ endwhile
+enddef
+
+# Returns true when the "lspserver" has "feature" enabled.
+# By default, all the features of a lsp server are enabled.
+def FeatureEnabled(lspserver: dict<any>, feature: string): bool
+ return lspserver.features->get(feature, true)
+enddef
+
+# Retrieve the Workspace configuration asked by the server.
+# Request: workspace/configuration
+def WorkspaceConfigGet(lspserver: dict<any>, configItem: dict<any>): dict<any>
+ if lspserver.workspaceConfig->empty()
+ return {}
+ endif
+ if !configItem->has_key('section') || configItem.section->empty()
+ return lspserver.workspaceConfig
+ endif
+ var config: dict<any> = lspserver.workspaceConfig
+ for part in configItem.section->split('\.')
+ if !config->has_key(part)
+ return {}
+ endif
+ config = config[part]
+ endfor
+ return config
+enddef
+
+# Update semantic highlighting for buffer "bnr"
+# Request: textDocument/semanticTokens/full or
+# textDocument/semanticTokens/full/delta
+def SemanticHighlightUpdate(lspserver: dict<any>, bnr: number)
+ if !lspserver.isSemanticTokensProvider
+ return
+ endif
+
+ # Send the pending buffer changes to the language server
+ bnr->listener_flush()
+
+ var method = 'textDocument/semanticTokens/full'
+ var params: dict<any> = {
+ textDocument: {
+ uri: util.LspBufnrToUri(bnr)
+ }
+ }
+
+ # Should we send a semantic tokens delta request instead of a full request?
+ if lspserver.semanticTokensDelta
+ var prevResultId: string = ''
+ prevResultId = bnr->getbufvar('LspSemanticResultId', '')
+ if prevResultId != ''
+ # semantic tokens delta request
+ params.previousResultId = prevResultId
+ method ..= '/delta'
+ endif
+ endif
+
+ var reply = lspserver.rpc(method, params)
+
+ if reply->empty() || reply.result->empty()
+ return
+ endif
+
+ semantichighlight.UpdateTokens(lspserver, bnr, reply.result)
+enddef
+
+# Send a "workspace/didChangeConfiguration" notification to the language
+# server.
+def SendWorkspaceConfig(lspserver: dict<any>)
+ # Params: DidChangeConfigurationParams
+ var params = {settings: lspserver.workspaceConfig}
+ lspserver.sendNotification('workspace/didChangeConfiguration', params)
+enddef
+
+# Send a file/document opened notification to the language server.
+def TextdocDidOpen(lspserver: dict<any>, bnr: number, ftype: string): void
+ # Notification: 'textDocument/didOpen'
+ # Params: DidOpenTextDocumentParams
+ var params = {
+ textDocument: {
+ uri: util.LspBufnrToUri(bnr),
+ languageId: ftype,
+ # Use Vim 'changedtick' as the LSP document version number
+ version: bnr->getbufvar('changedtick'),
+ text: bnr->getbufline(1, '$')->join("\n") .. "\n"
+ }
+ }
+ lspserver.sendNotification('textDocument/didOpen', params)
+enddef
+
+# Send a file/document closed notification to the language server.
+def TextdocDidClose(lspserver: dict<any>, bnr: number): void
+ # Notification: 'textDocument/didClose'
+ # Params: DidCloseTextDocumentParams
+ var params = {
+ textDocument: {
+ uri: util.LspBufnrToUri(bnr)
+ }
+ }
+ lspserver.sendNotification('textDocument/didClose', params)
+enddef
+
+# Send a file/document change notification to the language server.
+# Params: DidChangeTextDocumentParams
+def TextdocDidChange(lspserver: dict<any>, bnr: number, start: number,
+ end: number, added: number,
+ changes: list<dict<number>>): void
+ # Notification: 'textDocument/didChange'
+ # Params: DidChangeTextDocumentParams
+
+ # var changeset: list<dict<any>>
+
+ ##### FIXME: Sending specific buffer changes to the LSP server doesn't
+ ##### work properly as the computed line range numbers is not correct.
+ ##### For now, send the entire buffer content to LSP server.
+ # # Range
+ # for change in changes
+ # var lines: string
+ # var start_lnum: number
+ # var end_lnum: number
+ # var start_col: number
+ # var end_col: number
+ # if change.added == 0
+ # # lines changed
+ # start_lnum = change.lnum - 1
+ # end_lnum = change.end - 1
+ # lines = getbufline(bnr, change.lnum, change.end - 1)->join("\n") .. "\n"
+ # start_col = 0
+ # end_col = 0
+ # elseif change.added > 0
+ # # lines added
+ # start_lnum = change.lnum - 1
+ # end_lnum = change.lnum - 1
+ # start_col = 0
+ # end_col = 0
+ # lines = getbufline(bnr, change.lnum, change.lnum + change.added - 1)->join("\n") .. "\n"
+ # else
+ # # lines removed
+ # start_lnum = change.lnum - 1
+ # end_lnum = change.lnum + (-change.added) - 1
+ # start_col = 0
+ # end_col = 0
+ # lines = ''
+ # endif
+ # var range: dict<dict<number>> = {'start': {'line': start_lnum, 'character': start_col}, 'end': {'line': end_lnum, 'character': end_col}}
+ # changeset->add({'range': range, 'text': lines})
+ # endfor
+
+ var params = {
+ textDocument: {
+ uri: util.LspBufnrToUri(bnr),
+ # Use Vim 'changedtick' as the LSP document version number
+ version: bnr->getbufvar('changedtick')
+ },
+ contentChanges: [
+ {text: bnr->getbufline(1, '$')->join("\n") .. "\n"}
+ ]
+ }
+ lspserver.sendNotification('textDocument/didChange', params)
+enddef
+
+# Return the current cursor position as a LSP position.
+# find_ident will search for a identifier in front of the cursor, just like
+# CTRL-] and c_CTRL-R_CTRL-W does.
+#
+# LSP line and column numbers start from zero, whereas Vim line and column
+# numbers start from one. The LSP column number is the character index in the
+# line and not the byte index in the line.
+def GetPosition(lspserver: dict<any>, find_ident: bool): dict<number>
+ var lnum: number = line('.') - 1
+ var col: number = charcol('.') - 1
+ var line = getline('.')
+
+ if find_ident
+ # 1. skip to start of identifier
+ while line[col] != '' && line[col] !~ '\k'
+ col = col + 1
+ endwhile
+
+ # 2. back up to start of identifier
+ while col > 0 && line[col - 1] =~ '\k'
+ col = col - 1
+ endwhile
+ endif
+
+ # Compute character index counting composing characters as separate
+ # characters
+ var pos = {line: lnum, character: util.GetCharIdxWithCompChar(line, col)}
+ lspserver.encodePosition(bufnr(), pos)
+
+ return pos
+enddef
+
+# Return the current file name and current cursor position as a LSP
+# TextDocumentPositionParams structure
+def GetTextDocPosition(lspserver: dict<any>, find_ident: bool): dict<dict<any>>
+ # interface TextDocumentIdentifier
+ # interface Position
+ return {textDocument: {uri: util.LspFileToUri(@%)},
+ position: lspserver.getPosition(find_ident)}
+enddef
+
+# Get a list of completion items.
+# Request: "textDocument/completion"
+# Param: CompletionParams
+def GetCompletion(lspserver: dict<any>, triggerKind_arg: number, triggerChar: string): void
+ # Check whether LSP server supports completion
+ if !lspserver.isCompletionProvider
+ util.ErrMsg('LSP server does not support completion')
+ return
+ endif
+
+ var fname = @%
+ if fname->empty()
+ return
+ endif
+
+ # interface CompletionParams
+ # interface TextDocumentPositionParams
+ var params = lspserver.getTextDocPosition(false)
+ # interface CompletionContext
+ params.context = {triggerKind: triggerKind_arg, triggerCharacter: triggerChar}
+
+ lspserver.rpc_a('textDocument/completion', params,
+ completion.CompletionReply)
+enddef
+
+# Get lazy properties for a completion item.
+# Request: "completionItem/resolve"
+# Param: CompletionItem
+def ResolveCompletion(lspserver: dict<any>, item: dict<any>, sync: bool = false): dict<any>
+ # Check whether LSP server supports completion item resolve
+ if !lspserver.isCompletionResolveProvider
+ return {}
+ endif
+
+ # interface CompletionItem
+ if sync
+ var reply = lspserver.rpc('completionItem/resolve', item)
+ if !reply->empty() && !reply.result->empty()
+ return reply.result
+ endif
+ else
+ lspserver.rpc_a('completionItem/resolve', item,
+ completion.CompletionResolveReply)
+ endif
+ return {}
+enddef
+
+# Jump to or peek a symbol location.
+#
+# Send 'msg' to a LSP server and process the reply. 'msg' is one of the
+# following:
+# textDocument/definition
+# textDocument/declaration
+# textDocument/typeDefinition
+# textDocument/implementation
+#
+# Process the LSP server reply and jump to the symbol location. Before
+# jumping to the symbol location, save the current cursor position in the tag
+# stack.
+#
+# If 'peekSymbol' is true, then display the symbol location in the preview
+# window but don't jump to the symbol location.
+#
+# Result: Location | Location[] | LocationLink[] | null
+def GotoSymbolLoc(lspserver: dict<any>, msg: string, peekSymbol: bool,
+ cmdmods: string, count: number)
+ var reply = lspserver.rpc(msg, lspserver.getTextDocPosition(true), false)
+ if reply->empty() || reply.result->empty()
+ var emsg: string
+ if msg == 'textDocument/declaration'
+ emsg = 'symbol declaration is not found'
+ elseif msg == 'textDocument/typeDefinition'
+ emsg = 'symbol type definition is not found'
+ elseif msg == 'textDocument/implementation'
+ emsg = 'symbol implementation is not found'
+ else
+ emsg = 'symbol definition is not found'
+ endif
+
+ util.WarnMsg(emsg)
+ return
+ endif
+
+ var result = reply.result
+ var location: dict<any>
+ if result->type() == v:t_list
+ if count == 0
+ # When there are multiple symbol locations, and a specific one isn't
+ # requested with 'count', display the locations in a location list.
+ if result->len() > 1
+ var title: string = ''
+ if msg == 'textDocument/declaration'
+ title = 'Declarations'
+ elseif msg == 'textDocument/typeDefinition'
+ title = 'Type Definitions'
+ elseif msg == 'textDocument/implementation'
+ title = 'Implementations'
+ else
+ title = 'Definitions'
+ endif
+
+ if lspserver.needOffsetEncoding
+ # Decode the position encoding in all the symbol locations
+ result->map((_, loc) => {
+ lspserver.decodeLocation(loc)
+ return loc
+ })
+ endif
+
+ symbol.ShowLocations(lspserver, result, peekSymbol, title)
+ return
+ endif
+ endif
+
+ # Select the location requested in 'count'
+ var idx = count - 1
+ if idx >= result->len()
+ idx = result->len() - 1
+ endif
+ location = result[idx]
+ else
+ location = result
+ endif
+ lspserver.decodeLocation(location)
+
+ symbol.GotoSymbol(lspserver, location, peekSymbol, cmdmods)
+enddef
+
+# Request: "textDocument/definition"
+# Param: DefinitionParams
+def GotoDefinition(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
+ # Check whether LSP server supports jumping to a definition
+ if !lspserver.isDefinitionProvider
+ util.ErrMsg('Jumping to a symbol definition is not supported')
+ return
+ endif
+
+ # interface DefinitionParams
+ # interface TextDocumentPositionParams
+ GotoSymbolLoc(lspserver, 'textDocument/definition', peek, cmdmods, count)
+enddef
+
+# Request: "textDocument/declaration"
+# Param: DeclarationParams
+def GotoDeclaration(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
+ # Check whether LSP server supports jumping to a declaration
+ if !lspserver.isDeclarationProvider
+ util.ErrMsg('Jumping to a symbol declaration is not supported')
+ return
+ endif
+
+ # interface DeclarationParams
+ # interface TextDocumentPositionParams
+ GotoSymbolLoc(lspserver, 'textDocument/declaration', peek, cmdmods, count)
+enddef
+
+# Request: "textDocument/typeDefinition"
+# Param: TypeDefinitionParams
+def GotoTypeDef(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
+ # Check whether LSP server supports jumping to a type definition
+ if !lspserver.isTypeDefinitionProvider
+ util.ErrMsg('Jumping to a symbol type definition is not supported')
+ return
+ endif
+
+ # interface TypeDefinitionParams
+ # interface TextDocumentPositionParams
+ GotoSymbolLoc(lspserver, 'textDocument/typeDefinition', peek, cmdmods, count)
+enddef
+
+# Request: "textDocument/implementation"
+# Param: ImplementationParams
+def GotoImplementation(lspserver: dict<any>, peek: bool, cmdmods: string, count: number)
+ # Check whether LSP server supports jumping to a implementation
+ if !lspserver.isImplementationProvider
+ util.ErrMsg('Jumping to a symbol implementation is not supported')
+ return
+ endif
+
+ # interface ImplementationParams
+ # interface TextDocumentPositionParams
+ GotoSymbolLoc(lspserver, 'textDocument/implementation', peek, cmdmods, count)
+enddef
+
+# Request: "textDocument/switchSourceHeader"
+# Param: TextDocumentIdentifier
+# Clangd specific extension
+def SwitchSourceHeader(lspserver: dict<any>)
+ var param = {
+ uri: util.LspFileToUri(@%)
+ }
+ var reply = lspserver.rpc('textDocument/switchSourceHeader', param)
+ if reply->empty() || reply.result->empty()
+ util.WarnMsg('Source/Header file is not found')
+ return
+ endif
+
+ # process the 'textDocument/switchSourceHeader' reply from the LSP server
+ # Result: URI | null
+ var fname = util.LspUriToFile(reply.result)
+ # TODO: Add support for cmd modifiers
+ if (&modified && !&hidden) || &buftype != ''
+ # if the current buffer has unsaved changes and 'hidden' is not set,
+ # or if the current buffer is a special buffer, then ask to save changes
+ exe $'confirm edit {fname}'
+ else
+ exe $'edit {fname}'
+ endif
+enddef
+
+# get symbol signature help.
+# Request: "textDocument/signatureHelp"
+# Param: SignatureHelpParams
+def ShowSignature(lspserver: dict<any>): void
+ # Check whether LSP server supports signature help
+ if !lspserver.isSignatureHelpProvider
+ util.ErrMsg('LSP server does not support signature help')
+ return
+ endif
+
+ # interface SignatureHelpParams
+ # interface TextDocumentPositionParams
+ var params = lspserver.getTextDocPosition(false)
+ lspserver.rpc_a('textDocument/signatureHelp', params,
+ signature.SignatureHelp)
+enddef
+
+# Send a file/document saved notification to the language server
+def DidSaveFile(lspserver: dict<any>, bnr: number): void
+ # Check whether the LSP server supports the didSave notification
+ if !lspserver.supportsDidSave
+ # LSP server doesn't support text document synchronization
+ return
+ endif
+
+ # Notification: 'textDocument/didSave'
+ # Params: DidSaveTextDocumentParams
+ var params: dict<any> = {textDocument: {uri: util.LspBufnrToUri(bnr)}}
+
+ if lspserver.caps.textDocumentSync->type() == v:t_dict
+ && lspserver.caps.textDocumentSync->has_key('save')
+ if lspserver.caps.textDocumentSync.save->type() == v:t_dict
+ && lspserver.caps.textDocumentSync.save->has_key('includeText')
+ && lspserver.caps.textDocumentSync.save.includeText
+ params.text = bnr->getbufline(1, '$')->join("\n") .. "\n"
+ endif
+ endif
+
+ lspserver.sendNotification('textDocument/didSave', params)
+enddef
+
+# get the hover information
+# Request: "textDocument/hover"
+# Param: HoverParams
+def ShowHoverInfo(lspserver: dict<any>, cmdmods: string): void
+ # Check whether LSP server supports getting hover information.
+ # caps->hoverProvider can be a "boolean" or "HoverOptions"
+ if !lspserver.isHoverProvider
+ return
+ endif
+
+ # interface HoverParams
+ # interface TextDocumentPositionParams
+ var params = lspserver.getTextDocPosition(false)
+ lspserver.rpc_a('textDocument/hover', params, (_, reply) => {
+ hover.HoverReply(lspserver, reply, cmdmods)
+ })
+enddef
+
+# Request: "textDocument/references"
+# Param: ReferenceParams
+def ShowReferences(lspserver: dict<any>, peek: bool): void
+ # Check whether LSP server supports getting reference information
+ if !lspserver.isReferencesProvider
+ util.ErrMsg('LSP server does not support showing references')
+ return
+ endif
+
+ # interface ReferenceParams
+ # interface TextDocumentPositionParams
+ var param: dict<any>
+ param = lspserver.getTextDocPosition(true)
+ param.context = {includeDeclaration: true}
+ var reply = lspserver.rpc('textDocument/references', param)
+
+ # Result: Location[] | null
+ if reply->empty() || reply.result->empty()
+ util.WarnMsg('No references found')
+ return
+ endif
+
+ if lspserver.needOffsetEncoding
+ # Decode the position encoding in all the reference locations
+ reply.result->map((_, loc) => {
+ lspserver.decodeLocation(loc)
+ return loc
+ })
+ endif
+
+ symbol.ShowLocations(lspserver, reply.result, peek, 'Symbol References')
+enddef
+
+# process the 'textDocument/documentHighlight' reply from the LSP server
+# Result: DocumentHighlight[] | null
+def DocHighlightReply(lspserver: dict<any>, docHighlightReply: any,
+ bnr: number, cmdmods: string): void
+ if docHighlightReply->empty()
+ if cmdmods !~ 'silent'
+ util.WarnMsg($'No highlight for the current position')
+ endif
+ return
+ endif
+
+ for docHL in docHighlightReply
+ lspserver.decodeRange(bnr, docHL.range)
+ var kind: number = docHL->get('kind', 1)
+ var propName: string
+ if kind == 2
+ # Read-access
+ propName = 'LspReadRef'
+ elseif kind == 3
+ # Write-access
+ propName = 'LspWriteRef'
+ else
+ # textual reference
+ propName = 'LspTextRef'
+ endif
+ try
+ var docHL_range = docHL.range
+ var docHL_start = docHL_range.start
+ var docHL_end = docHL_range.end
+ prop_add(docHL_start.line + 1,
+ util.GetLineByteFromPos(bnr, docHL_start) + 1,
+ {end_lnum: docHL_end.line + 1,
+ end_col: util.GetLineByteFromPos(bnr, docHL_end) + 1,
+ bufnr: bnr,
+ type: propName})
+ catch /E966\|E964/ # Invalid lnum | Invalid col
+ # Highlight replies arrive asynchronously and the document might have
+ # been modified in the mean time. As the reply is stale, ignore invalid
+ # line number and column number errors.
+ endtry
+ endfor
+enddef
+
+# Request: "textDocument/documentHighlight"
+# Param: DocumentHighlightParams
+def DocHighlight(lspserver: dict<any>, bnr: number, cmdmods: string): void
+ # Check whether LSP server supports getting highlight information
+ if !lspserver.isDocumentHighlightProvider
+ util.ErrMsg('LSP server does not support document highlight')
+ return
+ endif
+
+ # Send the pending buffer changes to the language server
+ bnr->listener_flush()
+
+ # interface DocumentHighlightParams
+ # interface TextDocumentPositionParams
+ var params = lspserver.getTextDocPosition(false)
+ lspserver.rpc_a('textDocument/documentHighlight', params, (_, reply) => {
+ DocHighlightReply(lspserver, reply, bufnr(), cmdmods)
+ })
+enddef
+
+# Request: "textDocument/documentSymbol"
+# Param: DocumentSymbolParams
+def GetDocSymbols(lspserver: dict<any>, fname: string, showOutline: bool): void
+ # Check whether LSP server supports getting document symbol information
+ if !lspserver.isDocumentSymbolProvider
+ util.ErrMsg('LSP server does not support getting list of symbols')
+ return
+ endif
+
+ # interface DocumentSymbolParams
+ # interface TextDocumentIdentifier
+ var params = {textDocument: {uri: util.LspFileToUri(fname)}}
+ lspserver.rpc_a('textDocument/documentSymbol', params, (_, reply) => {
+ if showOutline
+ symbol.DocSymbolOutline(lspserver, reply, fname)
+ else
+ symbol.DocSymbolPopup(lspserver, reply, fname)
+ endif
+ })
+enddef
+
+# Request: "textDocument/formatting"
+# Param: DocumentFormattingParams
+# or
+# Request: "textDocument/rangeFormatting"
+# Param: DocumentRangeFormattingParams
+def TextDocFormat(lspserver: dict<any>, fname: string, rangeFormat: bool,
+ start_lnum: number, end_lnum: number)
+ # Check whether LSP server supports formatting documents
+ if !lspserver.isDocumentFormattingProvider
+ util.ErrMsg('LSP server does not support formatting documents')
+ return
+ endif
+
+ var cmd: string
+ if rangeFormat
+ cmd = 'textDocument/rangeFormatting'
+ else
+ cmd = 'textDocument/formatting'
+ endif
+
+ # interface DocumentFormattingParams
+ # interface TextDocumentIdentifier
+ # interface FormattingOptions
+ var fmtopts: dict<any> = {
+ tabSize: shiftwidth(),
+ insertSpaces: &expandtab ? true : false,
+ }
+ var param = {
+ textDocument: {
+ uri: util.LspFileToUri(fname)
+ },
+ options: fmtopts
+ }
+
+ if rangeFormat
+ var r: dict<dict<number>> = {
+ start: {line: start_lnum - 1, character: 0},
+ end: {line: end_lnum - 1, character: charcol([end_lnum, '$']) - 1}}
+ param.range = r
+ endif
+
+ var reply = lspserver.rpc(cmd, param)
+
+ # result: TextEdit[] | null
+
+ if reply->empty() || reply.result->empty()
+ # nothing to format
+ return
+ endif
+
+ var bnr: number = fname->bufnr()
+ if bnr == -1
+ # file is already removed
+ return
+ endif
+
+ if lspserver.needOffsetEncoding
+ # Decode the position encoding in all the reference locations
+ reply.result->map((_, textEdit) => {
+ lspserver.decodeRange(bnr, textEdit.range)
+ return textEdit
+ })
+ endif
+
+ # interface TextEdit
+ # Apply each of the text edit operations
+ var save_cursor: list<number> = getcurpos()
+ textedit.ApplyTextEdits(bnr, reply.result)
+ save_cursor->setpos('.')
+enddef
+
+# Request: "textDocument/prepareCallHierarchy"
+def PrepareCallHierarchy(lspserver: dict<any>): dict<any>
+ # interface CallHierarchyPrepareParams
+ # interface TextDocumentPositionParams
+ var param: dict<any>
+ param = lspserver.getTextDocPosition(false)
+ var reply = lspserver.rpc('textDocument/prepareCallHierarchy', param)
+ if reply->empty() || reply.result->empty()
+ return {}
+ endif
+
+ # Result: CallHierarchyItem[] | null
+ var choice: number = 1
+ if reply.result->len() > 1
+ var items: list<string> = ['Select a Call Hierarchy Item:']
+ for i in reply.result->len()->range()
+ items->add(printf("%d. %s", i + 1, reply.result[i].name))
+ endfor
+ choice = items->inputlist()
+ if choice < 1 || choice > items->len()
+ return {}
+ endif
+ endif
+
+ return reply.result[choice - 1]
+enddef
+
+# Request: "callHierarchy/incomingCalls"
+def IncomingCalls(lspserver: dict<any>, fname: string)
+ # Check whether LSP server supports call hierarchy
+ if !lspserver.isCallHierarchyProvider
+ util.ErrMsg('LSP server does not support call hierarchy')
+ return
+ endif
+
+ callhier.IncomingCalls(lspserver)
+enddef
+
+def GetIncomingCalls(lspserver: dict<any>, item_arg: dict<any>): any
+ # Request: "callHierarchy/incomingCalls"
+ # Param: CallHierarchyIncomingCallsParams
+ var param = {
+ item: item_arg
+ }
+ var reply = lspserver.rpc('callHierarchy/incomingCalls', param)
+ if reply->empty()
+ return null
+ endif
+
+ if lspserver.needOffsetEncoding
+ # Decode the position encoding in all the incoming call locations
+ var bnr = util.LspUriToBufnr(item_arg.uri)
+ reply.result->map((_, hierItem) => {
+ lspserver.decodeRange(bnr, hierItem.from.range)
+ return hierItem
+ })
+ endif
+
+ return reply.result
+enddef
+
+# Request: "callHierarchy/outgoingCalls"
+def OutgoingCalls(lspserver: dict<any>, fname: string)
+ # Check whether LSP server supports call hierarchy
+ if !lspserver.isCallHierarchyProvider
+ util.ErrMsg('LSP server does not support call hierarchy')
+ return
+ endif
+
+ callhier.OutgoingCalls(lspserver)
+enddef
+
+def GetOutgoingCalls(lspserver: dict<any>, item_arg: dict<any>): any
+ # Request: "callHierarchy/outgoingCalls"
+ # Param: CallHierarchyOutgoingCallsParams
+ var param = {
+ item: item_arg
+ }
+ var reply = lspserver.rpc('callHierarchy/outgoingCalls', param)
+ if reply->empty()
+ return null
+ endif
+
+ if lspserver.needOffsetEncoding
+ # Decode the position encoding in all the outgoing call locations
+ var bnr = util.LspUriToBufnr(item_arg.uri)
+ reply.result->map((_, hierItem) => {
+ lspserver.decodeRange(bnr, hierItem.to.range)
+ return hierItem
+ })
+ endif
+
+ return reply.result
+enddef
+
+# Request: "textDocument/inlayHint"
+# Inlay hints.
+def InlayHintsShow(lspserver: dict<any>, bnr: number)
+ # Check whether LSP server supports type hierarchy
+ if !lspserver.isInlayHintProvider && !lspserver.isClangdInlayHintsProvider
+ util.ErrMsg('LSP server does not support inlay hint')
+ return
+ endif
+
+ # Send the pending buffer changes to the language server
+ bnr->listener_flush()
+
+ var binfo = bnr->getbufinfo()
+ if binfo->empty()
+ return
+ endif
+ var lastlnum = binfo[0].linecount
+ var lastline = bnr->getbufline('$')
+ var lastcol = 1
+ if !lastline->empty() && !lastline[0]->empty()
+ lastcol = lastline[0]->strchars()
+ endif
+ var param = {
+ textDocument: {uri: util.LspBufnrToUri(bnr)},
+ range:
+ {
+ start: {line: 0, character: 0},
+ end: {line: lastlnum - 1, character: lastcol - 1}
+ }
+ }
+
+ lspserver.encodeRange(bnr, param.range)
+
+ var msg: string
+ if lspserver.isClangdInlayHintsProvider
+ # clangd-style inlay hints
+ msg = 'clangd/inlayHints'
+ else
+ msg = 'textDocument/inlayHint'
+ endif
+ var reply = lspserver.rpc_a(msg, param, (_, reply) => {
+ inlayhints.InlayHintsReply(lspserver, bnr, reply)
+ })
+enddef
+
+def DecodeTypeHierarchy(lspserver: dict<any>, isSuper: bool, typeHier: dict<any>)
+ if !lspserver.needOffsetEncoding
+ return
+ endif
+ var bnr = util.LspUriToBufnr(typeHier.uri)
+ lspserver.decodeRange(bnr, typeHier.range)
+ lspserver.decodeRange(bnr, typeHier.selectionRange)
+ var subType: list<dict<any>>
+ if isSuper
+ subType = typeHier->get('parents', [])
+ else
+ subType = typeHier->get('children', [])
+ endif
+ if !subType->empty()
+ # Decode the position encoding in all the type hierarchy items
+ subType->map((_, typeHierItem) => {
+ DecodeTypeHierarchy(lspserver, isSuper, typeHierItem)
+ return typeHierItem
+ })
+ endif
+enddef
+
+# Request: "textDocument/typehierarchy"
+# Support the clangd version of type hierarchy retrieval method.
+# The method described in the LSP 3.17.0 standard is not supported as clangd
+# doesn't support that method.
+def TypeHierarchy(lspserver: dict<any>, direction: number)
+ # Check whether LSP server supports type hierarchy
+ if !lspserver.isTypeHierarchyProvider
+ util.ErrMsg('LSP server does not support type hierarchy')
+ return
+ endif
+
+ # interface TypeHierarchy
+ # interface TextDocumentPositionParams
+ var param: dict<any>
+ param = lspserver.getTextDocPosition(false)
+ # 0: children, 1: parent, 2: both
+ param.direction = direction
+ param.resolve = 5
+ var reply = lspserver.rpc('textDocument/typeHierarchy', param)
+ if reply->empty() || reply.result->empty()
+ util.WarnMsg('No type hierarchy available')
+ return
+ endif
+
+ var isSuper = (direction == 1)
+
+ DecodeTypeHierarchy(lspserver, isSuper, reply.result)
+
+ typehier.ShowTypeHierarchy(lspserver, isSuper, reply.result)
+enddef
+
+# Decode the ranges in "WorkspaceEdit"
+def DecodeWorkspaceEdit(lspserver: dict<any>, workspaceEdit: dict<any>)
+ if !lspserver.needOffsetEncoding
+ return
+ endif
+ if workspaceEdit->has_key('changes')
+ for [uri, changes] in workspaceEdit.changes->items()
+ var bnr: number = util.LspUriToBufnr(uri)
+ if bnr <= 0
+ continue
+ endif
+ # Decode the position encoding in all the text edit locations
+ changes->map((_, textEdit) => {
+ lspserver.decodeRange(bnr, textEdit.range)
+ return textEdit
+ })
+ endfor
+ endif
+
+ if workspaceEdit->has_key('documentChanges')
+ for change in workspaceEdit.documentChanges
+ if !change->has_key('kind')
+ var bnr: number = util.LspUriToBufnr(change.textDocument.uri)
+ if bnr <= 0
+ continue
+ endif
+ # Decode the position encoding in all the text edit locations
+ change.edits->map((_, textEdit) => {
+ lspserver.decodeRange(bnr, textEdit.range)
+ return textEdit
+ })
+ endif
+ endfor
+ endif
+enddef
+
+# Request: "textDocument/rename"
+# Param: RenameParams
+def RenameSymbol(lspserver: dict<any>, newName: string)
+ # Check whether LSP server supports rename operation
+ if !lspserver.isRenameProvider
+ util.ErrMsg('LSP server does not support rename operation')
+ return
+ endif
+
+ # interface RenameParams
+ # interface TextDocumentPositionParams
+ var param: dict<any> = {}
+ param = lspserver.getTextDocPosition(true)
+ param.newName = newName
+
+ var reply = lspserver.rpc('textDocument/rename', param)
+
+ # Result: WorkspaceEdit | null
+ if reply->empty() || reply.result->empty()
+ # nothing to rename
+ return
+ endif
+
+ # result: WorkspaceEdit
+ DecodeWorkspaceEdit(lspserver, reply.result)
+ textedit.ApplyWorkspaceEdit(reply.result)
+enddef
+
+# Decode the range in "CodeAction"
+def DecodeCodeAction(lspserver: dict<any>, actionList: list<dict<any>>)
+ if !lspserver.needOffsetEncoding
+ return
+ endif
+ actionList->map((_, act) => {
+ if !act->has_key('disabled') && act->has_key('edit')
+ DecodeWorkspaceEdit(lspserver, act.edit)
+ endif
+ return act
+ })
+enddef
+
+# Request: "textDocument/codeAction"
+# Param: CodeActionParams
+def CodeAction(lspserver: dict<any>, fname_arg: string, line1: number,
+ line2: number, query: string)
+ # Check whether LSP server supports code action operation
+ if !lspserver.isCodeActionProvider
+ util.ErrMsg('LSP server does not support code action operation')
+ return
+ endif
+
+ # interface CodeActionParams
+ var params: dict<any> = {}
+ var fname: string = fname_arg->fnamemodify(':p')
+ var bnr: number = fname_arg->bufnr()
+ var r: dict<dict<number>> = {
+ start: {
+ line: line1 - 1,
+ character: line1 == line2 ? util.GetCharIdxWithCompChar(getline('.'), charcol('.') - 1) : 0
+ },
+ end: {
+ line: line2 - 1,
+ character: util.GetCharIdxWithCompChar(getline(line2), charcol([line2, '$']) - 1)
+ }
+ }
+ lspserver.encodeRange(bnr, r)
+ params->extend({textDocument: {uri: util.LspFileToUri(fname)}, range: r})
+ var d: list<dict<any>> = []
+ for lnum in range(line1, line2)
+ var diagsInfo: list<dict<any>> = diag.GetDiagsByLine(bnr, lnum, lspserver)->deepcopy()
+ if lspserver.needOffsetEncoding
+ diagsInfo->map((_, di) => {
+ lspserver.encodeRange(bnr, di.range)
+ return di
+ })
+ endif
+ d->extend(diagsInfo)
+ endfor
+ params->extend({context: {diagnostics: d, triggerKind: 1}})
+
+ var reply = lspserver.rpc('textDocument/codeAction', params)
+
+ # Result: (Command | CodeAction)[] | null
+ if reply->empty() || reply.result->empty()
+ # no action can be performed
+ util.WarnMsg('No code action is available')
+ return
+ endif
+
+ DecodeCodeAction(lspserver, reply.result)
+
+ codeaction.ApplyCodeAction(lspserver, reply.result, query)
+enddef
+
+# Request: "textDocument/codeLens"
+# Param: CodeLensParams
+def CodeLens(lspserver: dict<any>, fname: string)
+ # Check whether LSP server supports code lens operation
+ if !lspserver.isCodeLensProvider
+ util.ErrMsg('LSP server does not support code lens operation')
+ return
+ endif
+
+ var params = {textDocument: {uri: util.LspFileToUri(fname)}}
+ var reply = lspserver.rpc('textDocument/codeLens', params)
+ if reply->empty() || reply.result->empty()
+ util.WarnMsg($'No code lens actions found for the current file')
+ return
+ endif
+
+ var bnr = fname->bufnr()
+
+ # Decode the position encoding in all the code lens items
+ if lspserver.needOffsetEncoding
+ reply.result->map((_, codeLensItem) => {
+ lspserver.decodeRange(bnr, codeLensItem.range)
+ return codeLensItem
+ })
+ endif
+
+ codelens.ProcessCodeLens(lspserver, bnr, reply.result)
+enddef
+
+# Request: "codeLens/resolve"
+# Param: CodeLens
+def ResolveCodeLens(lspserver: dict<any>, bnr: number,
+ codeLens: dict<any>): dict<any>
+ if !lspserver.isCodeLensResolveProvider
+ return {}
+ endif
+
+ if lspserver.needOffsetEncoding
+ lspserver.encodeRange(bnr, codeLens.range)
+ endif
+
+ var reply = lspserver.rpc('codeLens/resolve', codeLens)
+ if reply->empty()
+ return {}
+ endif
+
+ var codeLensItem: dict<any> = reply.result
+
+ # Decode the position encoding in the code lens item
+ if lspserver.needOffsetEncoding
+ lspserver.decodeRange(bnr, codeLensItem.range)
+ endif
+
+ return codeLensItem
+enddef
+
+# List project-wide symbols matching query string
+# Request: "workspace/symbol"
+# Param: WorkspaceSymbolParams
+def WorkspaceQuerySymbols(lspserver: dict<any>, query: string, firstCall: bool, cmdmods: string = '')
+ # Check whether the LSP server supports listing workspace symbols
+ if !lspserver.isWorkspaceSymbolProvider
+ util.ErrMsg('LSP server does not support listing workspace symbols')
+ return
+ endif
+
+ # Param: WorkspaceSymbolParams
+ var param = {
+ query: query
+ }
+ var reply = lspserver.rpc('workspace/symbol', param)
+ if reply->empty() || reply.result->empty()
+ util.WarnMsg($'Symbol "{query}" is not found')
+ return
+ endif
+
+ var symInfo: list<dict<any>> = reply.result
+
+ if lspserver.needOffsetEncoding
+ # Decode the position encoding in all the symbol locations
+ symInfo->map((_, sym) => {
+ if sym->has_key('location')
+ lspserver.decodeLocation(sym.location)
+ endif
+ return sym
+ })
+ endif
+
+ if firstCall && symInfo->len() == 1
+ # If there is only one symbol, then jump to the symbol location
+ var symLoc: dict<any> = symInfo[0]->get('location', {})
+ if !symLoc->empty()
+ symbol.GotoSymbol(lspserver, symLoc, false, cmdmods)
+ endif
+ else
+ symbol.WorkspaceSymbolPopup(lspserver, query, symInfo, cmdmods)
+ endif
+enddef
+
+# Add a workspace folder to the language server.
+def AddWorkspaceFolder(lspserver: dict<any>, dirName: string): void
+ if !lspserver.caps->has_key('workspace')
+ || !lspserver.caps.workspace->has_key('workspaceFolders')
+ || !lspserver.caps.workspace.workspaceFolders->has_key('supported')
+ || !lspserver.caps.workspace.workspaceFolders.supported
+ util.ErrMsg('LSP server does not support workspace folders')
+ return
+ endif
+
+ if lspserver.workspaceFolders->index(dirName) != -1
+ util.ErrMsg($'{dirName} is already part of this workspace')
+ return
+ endif
+
+ # Notification: 'workspace/didChangeWorkspaceFolders'
+ # Params: DidChangeWorkspaceFoldersParams
+ var params = {event: {added: [dirName], removed: []}}
+ lspserver.sendNotification('workspace/didChangeWorkspaceFolders', params)
+
+ lspserver.workspaceFolders->add(dirName)
+enddef
+
+# Remove a workspace folder from the language server.
+def RemoveWorkspaceFolder(lspserver: dict<any>, dirName: string): void
+ if !lspserver.caps->has_key('workspace')
+ || !lspserver.caps.workspace->has_key('workspaceFolders')
+ || !lspserver.caps.workspace.workspaceFolders->has_key('supported')
+ || !lspserver.caps.workspace.workspaceFolders.supported
+ util.ErrMsg('LSP server does not support workspace folders')
+ return
+ endif
+
+ var idx: number = lspserver.workspaceFolders->index(dirName)
+ if idx == -1
+ util.ErrMsg($'{dirName} is not currently part of this workspace')
+ return
+ endif
+
+ # Notification: "workspace/didChangeWorkspaceFolders"
+ # Param: DidChangeWorkspaceFoldersParams
+ var params = {event: {added: [], removed: [dirName]}}
+ lspserver.sendNotification('workspace/didChangeWorkspaceFolders', params)
+
+ lspserver.workspaceFolders->remove(idx)
+enddef
+
+def DecodeSelectionRange(lspserver: dict<any>, bnr: number, selRange: dict<any>)
+ lspserver.decodeRange(bnr, selRange.range)
+ if selRange->has_key('parent')
+ DecodeSelectionRange(lspserver, bnr, selRange.parent)
+ endif
+enddef
+
+# select the text around the current cursor location
+# Request: "textDocument/selectionRange"
+# Param: SelectionRangeParams
+def SelectionRange(lspserver: dict<any>, fname: string)
+ # Check whether LSP server supports selection ranges
+ if !lspserver.isSelectionRangeProvider
+ util.ErrMsg('LSP server does not support selection ranges')
+ return
+ endif
+
+ # clear the previous selection reply
+ lspserver.selection = {}
+
+ # interface SelectionRangeParams
+ # interface TextDocumentIdentifier
+ var param = {
+ textDocument: {
+ uri: util.LspFileToUri(fname)
+ },
+ positions: [lspserver.getPosition(false)]
+ }
+ var reply = lspserver.rpc('textDocument/selectionRange', param)
+
+ if reply->empty() || reply.result->empty()
+ return
+ endif
+
+ # Decode the position encoding in all the selection range items
+ if lspserver.needOffsetEncoding
+ var bnr = fname->bufnr()
+ reply.result->map((_, selItem) => {
+ DecodeSelectionRange(lspserver, bnr, selItem)
+ return selItem
+ })
+ endif
+
+ selection.SelectionStart(lspserver, reply.result)
+enddef
+
+# Expand the previous selection or start a new one
+def SelectionExpand(lspserver: dict<any>)
+ # Check whether LSP server supports selection ranges
+ if !lspserver.isSelectionRangeProvider
+ util.ErrMsg('LSP server does not support selection ranges')
+ return
+ endif
+
+ selection.SelectionModify(lspserver, true)
+enddef
+
+# Shrink the previous selection or start a new one
+def SelectionShrink(lspserver: dict<any>)
+ # Check whether LSP server supports selection ranges
+ if !lspserver.isSelectionRangeProvider
+ util.ErrMsg('LSP server does not support selection ranges')
+ return
+ endif
+
+ selection.SelectionModify(lspserver, false)
+enddef
+
+# fold the entire document
+# Request: "textDocument/foldingRange"
+# Param: FoldingRangeParams
+def FoldRange(lspserver: dict<any>, fname: string)
+ # Check whether LSP server supports fold ranges
+ if !lspserver.isFoldingRangeProvider
+ util.ErrMsg('LSP server does not support folding')
+ return
+ endif
+
+ # interface FoldingRangeParams
+ # interface TextDocumentIdentifier
+ var params = {textDocument: {uri: util.LspFileToUri(fname)}}
+ var reply = lspserver.rpc('textDocument/foldingRange', params)
+ if reply->empty() || reply.result->empty()
+ return
+ endif
+
+ # result: FoldingRange[]
+ var end_lnum: number
+ var last_lnum: number = line('$')
+ for foldRange in reply.result
+ end_lnum = foldRange.endLine + 1
+ if end_lnum < foldRange.startLine + 2
+ end_lnum = foldRange.startLine + 2
+ endif
+ exe $':{foldRange.startLine + 2}, {end_lnum}fold'
+ # Open all the folds, otherwise the subsequently created folds are not
+ # correct.
+ :silent! foldopen!
+ endfor
+
+ if &foldcolumn == 0
+ :setlocal foldcolumn=2
+ endif
+enddef
+
+# process the 'workspace/executeCommand' reply from the LSP server
+# Result: any | null
+def WorkspaceExecuteReply(lspserver: dict<any>, execReply: any)
+ # Nothing to do for the reply
+enddef
+
+# Request the LSP server to execute a command
+# Request: workspace/executeCommand
+# Params: ExecuteCommandParams
+def ExecuteCommand(lspserver: dict<any>, cmd: dict<any>)
+ # Need to check for lspserver.caps.executeCommandProvider?
+ var params: dict<any> = {}
+ params.command = cmd.command
+ if cmd->has_key('arguments')
+ params.arguments = cmd.arguments
+ endif
+
+ lspserver.rpc_a('workspace/executeCommand', params, WorkspaceExecuteReply)
+enddef
+
+# Display the LSP server capabilities (received during the initialization
+# stage).
+def GetCapabilities(lspserver: dict<any>): list<string>
+ var l = []
+ var heading = $"'{lspserver.path}' Language Server Capabilities"
+ var underlines = repeat('=', heading->len())
+ l->extend([heading, underlines])
+ for k in lspserver.caps->keys()->sort()
+ l->add($'{k}: {lspserver.caps[k]->string()}')
+ endfor
+ return l
+enddef
+
+# Display the LSP server initialize request and result
+def GetInitializeRequest(lspserver: dict<any>): list<string>
+ var l = []
+ var heading = $"'{lspserver.path}' Language Server Initialize Request"
+ var underlines = repeat('=', heading->len())
+ l->extend([heading, underlines])
+ if lspserver->has_key('rpcInitializeRequest')
+ for k in lspserver.rpcInitializeRequest->keys()->sort()
+ l->add($'{k}: {lspserver.rpcInitializeRequest[k]->string()}')
+ endfor
+ endif
+ return l
+enddef
+
+# Store a log or trace message received from the language server.
+def AddMessage(lspserver: dict<any>, msgType: string, newMsg: string)
+ # A single message may contain multiple lines separate by newline
+ var msgs = newMsg->split("\n")
+ lspserver.messages->add($'{strftime("%m/%d/%y %T")}: [{msgType}]: {msgs[0]}')
+ lspserver.messages->extend(msgs[1 : ])
+ # Keep only the last 500 messages to reduce the memory usage
+ if lspserver.messages->len() >= 600
+ lspserver.messages = lspserver.messages[-500 : ]
+ endif
+enddef
+
+# Display the log messages received from the LSP server (window/logMessage)
+def GetMessages(lspserver: dict<any>): list<string>
+ if lspserver.messages->empty()
+ return [$'No messages received from "{lspserver.name}" server']
+ endif
+
+ var l = []
+ var heading = $"'{lspserver.path}' Language Server Messages"
+ var underlines = repeat('=', heading->len())
+ l->extend([heading, underlines])
+ l->extend(lspserver.messages)
+ return l
+enddef
+
+# Send a 'textDocument/definition' request to the LSP server to get the
+# location where the symbol under the cursor is defined and return a list of
+# Dicts in a format accepted by the 'tagfunc' option.
+# Returns null if the LSP server doesn't support getting the location of a
+# symbol definition or the symbol is not defined.
+def TagFunc(lspserver: dict<any>, pat: string, flags: string, info: dict<any>): any
+ # Check whether LSP server supports getting the location of a definition
+ if !lspserver.isDefinitionProvider
+ return null
+ endif
+
+ # interface DefinitionParams
+ # interface TextDocumentPositionParams
+ var reply = lspserver.rpc('textDocument/definition',
+ lspserver.getTextDocPosition(false))
+ if reply->empty() || reply.result->empty()
+ return null
+ endif
+
+ var taglocations: list<dict<any>>
+ if reply.result->type() == v:t_list
+ taglocations = reply.result
+ else
+ taglocations = [reply.result]
+ endif
+
+ if lspserver.needOffsetEncoding
+ # Decode the position encoding in all the reference locations
+ taglocations->map((_, loc) => {
+ lspserver.decodeLocation(loc)
+ return loc
+ })
+ endif
+
+ return symbol.TagFunc(lspserver, taglocations, pat)
+enddef
+
+# Returns unique ID used for identifying the various servers
+var UniqueServerIdCounter = 0
+def GetUniqueServerId(): number
+ UniqueServerIdCounter = UniqueServerIdCounter + 1
+ return UniqueServerIdCounter
+enddef
+
+export def NewLspServer(serverParams: dict<any>): dict<any>
+ var lspserver: dict<any> = {
+ id: GetUniqueServerId(),
+ name: serverParams.name,
+ path: serverParams.path,
+ args: serverParams.args->deepcopy(),
+ running: false,
+ ready: false,
+ job: v:none,
+ data: '',
+ nextID: 1,
+ caps: {},
+ requests: {},
+ callHierarchyType: '',
+ completionTriggerChars: [],
+ customNotificationHandlers: serverParams.customNotificationHandlers->deepcopy(),
+ customRequestHandlers: serverParams.customRequestHandlers->deepcopy(),
+ debug: serverParams.debug,
+ features: serverParams.features->deepcopy(),
+ forceOffsetEncoding: serverParams.forceOffsetEncoding,
+ initializationOptions: serverParams.initializationOptions->deepcopy(),
+ messages: [],
+ needOffsetEncoding: false,
+ omniCompletePending: false,
+ peekSymbolFilePopup: -1,
+ peekSymbolPopup: -1,
+ processDiagHandler: serverParams.processDiagHandler,
+ rootSearchFiles: serverParams.rootSearch->deepcopy(),
+ runIfSearchFiles: serverParams.runIfSearch->deepcopy(),
+ runUnlessSearchFiles: serverParams.runUnlessSearch->deepcopy(),
+ selection: {},
+ signaturePopup: -1,
+ syncInit: serverParams.syncInit,
+ traceLevel: serverParams.traceLevel,
+ typeHierFilePopup: -1,
+ typeHierPopup: -1,
+ workspaceConfig: serverParams.workspaceConfig->deepcopy(),
+ workspaceSymbolPopup: -1,
+ workspaceSymbolQuery: ''
+ }
+ lspserver.logfile = $'lsp-{lspserver.name}.log'
+ lspserver.errfile = $'lsp-{lspserver.name}.err'
+
+ # Add the LSP server functions
+ lspserver->extend({
+ startServer: function(StartServer, [lspserver]),
+ initServer: function(InitServer, [lspserver]),
+ stopServer: function(StopServer, [lspserver]),
+ shutdownServer: function(ShutdownServer, [lspserver]),
+ exitServer: function(ExitServer, [lspserver]),
+ setTrace: function(SetTrace, [lspserver]),
+ traceLog: function(TraceLog, [lspserver]),
+ errorLog: function(ErrorLog, [lspserver]),
+ nextReqID: function(NextReqID, [lspserver]),
+ createRequest: function(CreateRequest, [lspserver]),
+ createResponse: function(CreateResponse, [lspserver]),
+ sendResponse: function(SendResponse, [lspserver]),
+ sendMessage: function(SendMessage, [lspserver]),
+ sendNotification: function(SendNotification, [lspserver]),
+ rpc: function(Rpc, [lspserver]),
+ rpc_a: function(AsyncRpc, [lspserver]),
+ waitForResponse: function(WaitForResponse, [lspserver]),
+ processReply: function(handlers.ProcessReply, [lspserver]),
+ processNotif: function(handlers.ProcessNotif, [lspserver]),
+ processRequest: function(handlers.ProcessRequest, [lspserver]),
+ processMessages: function(handlers.ProcessMessages, [lspserver]),
+ encodePosition: function(offset.EncodePosition, [lspserver]),
+ decodePosition: function(offset.DecodePosition, [lspserver]),
+ encodeRange: function(offset.EncodeRange, [lspserver]),
+ decodeRange: function(offset.DecodeRange, [lspserver]),
+ encodeLocation: function(offset.EncodeLocation, [lspserver]),
+ decodeLocation: function(offset.DecodeLocation, [lspserver]),
+ getPosition: function(GetPosition, [lspserver]),
+ getTextDocPosition: function(GetTextDocPosition, [lspserver]),
+ featureEnabled: function(FeatureEnabled, [lspserver]),
+ textdocDidOpen: function(TextdocDidOpen, [lspserver]),
+ textdocDidClose: function(TextdocDidClose, [lspserver]),
+ textdocDidChange: function(TextdocDidChange, [lspserver]),
+ sendInitializedNotif: function(SendInitializedNotif, [lspserver]),
+ sendWorkspaceConfig: function(SendWorkspaceConfig, [lspserver]),
+ getCompletion: function(GetCompletion, [lspserver]),
+ resolveCompletion: function(ResolveCompletion, [lspserver]),
+ gotoDefinition: function(GotoDefinition, [lspserver]),
+ gotoDeclaration: function(GotoDeclaration, [lspserver]),
+ gotoTypeDef: function(GotoTypeDef, [lspserver]),
+ gotoImplementation: function(GotoImplementation, [lspserver]),
+ tagFunc: function(TagFunc, [lspserver]),
+ switchSourceHeader: function(SwitchSourceHeader, [lspserver]),
+ showSignature: function(ShowSignature, [lspserver]),
+ didSaveFile: function(DidSaveFile, [lspserver]),
+ hover: function(ShowHoverInfo, [lspserver]),
+ showReferences: function(ShowReferences, [lspserver]),
+ docHighlight: function(DocHighlight, [lspserver]),
+ getDocSymbols: function(GetDocSymbols, [lspserver]),
+ textDocFormat: function(TextDocFormat, [lspserver]),
+ prepareCallHierarchy: function(PrepareCallHierarchy, [lspserver]),
+ incomingCalls: function(IncomingCalls, [lspserver]),
+ getIncomingCalls: function(GetIncomingCalls, [lspserver]),
+ outgoingCalls: function(OutgoingCalls, [lspserver]),
+ getOutgoingCalls: function(GetOutgoingCalls, [lspserver]),
+ inlayHintsShow: function(InlayHintsShow, [lspserver]),
+ typeHierarchy: function(TypeHierarchy, [lspserver]),
+ renameSymbol: function(RenameSymbol, [lspserver]),
+ codeAction: function(CodeAction, [lspserver]),
+ codeLens: function(CodeLens, [lspserver]),
+ resolveCodeLens: function(ResolveCodeLens, [lspserver]),
+ workspaceQuery: function(WorkspaceQuerySymbols, [lspserver]),
+ addWorkspaceFolder: function(AddWorkspaceFolder, [lspserver]),
+ removeWorkspaceFolder: function(RemoveWorkspaceFolder, [lspserver]),
+ selectionRange: function(SelectionRange, [lspserver]),
+ selectionExpand: function(SelectionExpand, [lspserver]),
+ selectionShrink: function(SelectionShrink, [lspserver]),
+ foldRange: function(FoldRange, [lspserver]),
+ executeCommand: function(ExecuteCommand, [lspserver]),
+ workspaceConfigGet: function(WorkspaceConfigGet, [lspserver]),
+ semanticHighlightUpdate: function(SemanticHighlightUpdate, [lspserver]),
+ getCapabilities: function(GetCapabilities, [lspserver]),
+ getInitializeRequest: function(GetInitializeRequest, [lspserver]),
+ addMessage: function(AddMessage, [lspserver]),
+ getMessages: function(GetMessages, [lspserver])
+ })
+
+ return lspserver
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/markdown.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/markdown.vim
new file mode 100644
index 0000000..fd73cee
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/markdown.vim
@@ -0,0 +1,757 @@
+vim9script
+
+# Markdown parser
+# Refer to https://github.github.com/gfm/
+# for the GitHub Flavored Markdown specification.
+
+# TODO: different highlight for different heading level
+# TODO: links
+# TODO: pretty table
+
+
+# Container blocks
+var block_quote = '^ \{,3\}\zs> \='
+var list_marker = '[-+*]\|[0-9]\{1,9}[.)]'
+var list_item = $'^\%({list_marker}\)\ze\s*$\|^ \{{,3}}\zs\%({list_marker}\) \{{1,4}}\ze\S\|^ \{{,3}}\zs\%({list_marker}\) \{{5}}\ze\s*\S'
+# pattern to match list items
+export var list_pattern = $'^ *\%({list_marker}\) *'
+
+
+# Leaf blocks
+var blank_line = '^\s*$'
+var thematic_break = '^ \{,3\}\([-_*]\)\%(\s*\1\)\{2,\}\s*$'
+var code_fence = '^ \{,3\}\(`\{3,\}\|\~\{3,\}\)\s*\(\S*\)'
+var code_indent = '^ \{4\}\zs\s*\S.*'
+var paragraph = '^\s*\zs\S.\{-}\s*\ze$'
+
+var atx_heading = '^ \{,3}\zs\(#\{1,6}\) \s*\(.\{-}\)\s*\ze\%( #\{1,}\s*\)\=$'
+var setext_heading = '^ \{,3}\zs\%(=\{1,}\|-\{1,}\)\ze *$'
+var setext_heading_level = {"=": 1, "-": 2}
+
+var table_delimiter = '^|\=\zs *:\=-\{1,}:\= *\%(| *:\=-\{1,}:\= *\)*\ze|\=$'
+
+var punctuation = "[!\"#$%&'()*+,-./:;<=>?@[\\\\\\\]^_`{|}~]"
+
+# Setting text properties
+highlight LspBold term=bold cterm=bold gui=bold
+highlight LspItalic term=italic cterm=italic gui=italic
+highlight LspStrikeThrough term=strikethrough cterm=strikethrough gui=strikethrough
+prop_type_add('LspMarkdownBold', {highlight: 'LspBold'})
+prop_type_add('LspMarkdownItalic', {highlight: 'LspItalic'})
+prop_type_add('LspMarkdownStrikeThrough', {highlight: 'LspStrikeThrough'})
+prop_type_add('LspMarkdownHeading', {highlight: 'Function'})
+prop_type_add('LspMarkdownCode', {highlight: 'PreProc'})
+prop_type_add('LspMarkdownCodeBlock', {highlight: 'PreProc'})
+prop_type_add('LspMarkdownListMarker', {highlight: 'Special'})
+prop_type_add('LspMarkdownTableHeader', {highlight: 'Label'})
+prop_type_add('LspMarkdownTableMarker', {highlight: 'Special'})
+
+
+def GetMarkerProp(marker: string, col: number, ...opt: list<any>): dict<any>
+ if marker == 'list_item'
+ return {
+ type: 'LspMarkdownListMarker',
+ col: col,
+ length: opt[0]
+ }
+ elseif marker == 'code_block'
+ return {
+ type: 'LspMarkdownCodeBlock',
+ col: col,
+ end_lnum: opt[0],
+ end_col: opt[1]
+ }
+ elseif marker == 'heading'
+ return {
+ type: 'LspMarkdownHeading',
+ col: col,
+ length: opt[0]
+ }
+ elseif marker == 'table_header'
+ return {
+ type: 'LspMarkdownTableHeader',
+ col: col,
+ length: opt[0]
+ }
+ elseif marker == 'table_sep'
+ return {
+ type: 'LspMarkdownTableMarker',
+ col: col,
+ length: opt[0]
+ }
+ elseif marker == 'code_span'
+ return {
+ type: 'LspMarkdownCode',
+ col: col,
+ length: opt[0]
+ }
+ elseif marker == 'emphasis'
+ return {
+ type: 'LspMarkdownItalic',
+ col: col,
+ length: opt[0]
+ }
+ elseif marker == 'strong'
+ return {
+ type: 'LspMarkdownBold',
+ col: col,
+ length: opt[0]
+ }
+ elseif marker == 'strikethrough'
+ return {
+ type: 'LspMarkdownStrikeThrough',
+ col: col,
+ length: opt[0]
+ }
+ endif
+ return {}
+enddef
+
+def GetCodeSpans(text: string): list<dict<any>>
+ var code_spans = []
+ var pos = 0
+ while pos < text->len()
+ var backtick = text->matchstrpos('\\*`', pos)
+ if backtick[1] < 0
+ break
+ endif
+ if backtick[0]->len() % 2 == 0
+ # escaped backtick
+ pos = backtick[2]
+ continue
+ endif
+ pos = backtick[2] - 1
+ var code_span = text->matchstrpos('^\(`\+\)`\@!.\{-}`\@1<!\1`\@!', pos)
+ if code_span[1] < 0
+ break
+ endif
+ var code_text = text->matchstrpos('^\(`\+\)\%(\zs \+\ze\|\([ \n]\=\)\zs.\{-}\S.\{-}\ze\2\)`\@1<!\1`\@!', pos)
+ code_spans->add({
+ marker: '`',
+ start: [code_span[1], code_text[1]],
+ end: [code_text[2], code_span[2]]
+ })
+ pos = code_span[2]
+ endwhile
+ return code_spans
+enddef
+
+def Unescape(text: string, block_marker: string = ""): string
+ if block_marker == '`'
+ # line breaks do not occur inside code spans
+ return text->substitute('\n', ' ', 'g')
+ endif
+ # use 2 spaces instead of \ for hard line break
+ var result = text->substitute('\\\@<!\(\(\\\\\)*\)\\\n', '\1 \n', 'g')
+ # change soft line breaks
+ result = result->substitute(' \@<! \=\n', ' ', 'g')
+ # change hard line breaks
+ result = result->substitute(' \{2,}\n', '\n', 'g')
+ # replace non-breaking spaces with spaces
+ result = result->substitute('&nbsp;', ' ', 'g')
+ return result->substitute($'\\\({punctuation}\)', '\1', 'g')
+enddef
+
+def GetNextInlineDelimiter(text: string, start_pos: number, end_pos: number): dict<any>
+ var pos = start_pos
+ while pos < text->len()
+ # search the first delimiter char
+ var delimiter = text->matchstrpos('\\*[_*~]', pos)
+ if delimiter[1] < 0 || delimiter[1] > end_pos
+ return {}
+ endif
+ if delimiter[0]->len() % 2 == 0
+ # escaped delimiter char
+ pos = delimiter[2]
+ continue
+ endif
+ pos = delimiter[2] - 1
+ var delimiter_run = text->matchstrpos(
+ $'{delimiter[0][-1]->substitute("\\([*~]\\)", "\\\\\\1", "g")}\+',
+ pos)
+ if delimiter_run[0][0] == '~' && delimiter_run[0]->len() > 2
+ pos = delimiter_run[2]
+ continue
+ endif
+ var add_char = ''
+ if pos > 0
+ pos -= 1
+ add_char = '.'
+ endif
+ var delim_regex = delimiter_run[0]->substitute('\([*~]\)', '\\\1', 'g')
+ var is_left = text->match($'^{add_char}{delim_regex}\%(\s\|$\|{punctuation}\)\@!\|^{add_char}\%(\s\|^\|{punctuation}\)\@1<={delim_regex}{punctuation}', pos) >= 0
+ var is_right = text->match($'^{add_char}\%(\s\|^\|{punctuation}\)\@1<!{delim_regex}\|^{add_char}{punctuation}\@1<={delim_regex}\%(\s\|$\|{punctuation}\)', pos) >= 0
+ if !is_left && ! is_right
+ pos = delimiter_run[2]
+ continue
+ endif
+ if delimiter_run[0][0] == '_'
+ && text->match($'^\w{delimiter_run[0]}\w', pos) >= 0
+ # intraword emphasis is disallowed
+ pos = delimiter_run[2]
+ continue
+ endif
+ return {
+ marker: delimiter_run[0],
+ start: [delimiter_run[1], delimiter_run[2]],
+ left: is_left,
+ right: is_right
+ }
+ endwhile
+ return {}
+enddef
+
+def GetNextInlineBlock(text: string, blocks: list<any>, rel_pos: number): dict<any>
+ var result = {
+ text: '',
+ props: []
+ }
+ var cur = blocks->remove(0)
+ var pos = cur.start[1]
+ while blocks->len() > 0 && cur.end[0] >= blocks[0].start[0]
+ result.text ..= Unescape(text->strpart(pos, blocks[0].start[0] - pos), cur.marker[0])
+ # get nested block
+ var part = GetNextInlineBlock(text, blocks, rel_pos + result.text->len())
+ result.text ..= part.text
+ result.props += part.props
+ pos = part.end_pos
+ endwhile
+ result.text ..= Unescape(text->strpart(pos, cur.end[0] - pos), cur.marker[0])
+ # add props for current inline block
+ var prop_type = {
+ '`': 'code_span',
+ '_': 'emphasis',
+ '__': 'strong',
+ '*': 'emphasis',
+ '**': 'strong',
+ '~': 'strikethrough',
+ '~~': 'strikethrough'
+ }
+ result.props->insert(GetMarkerProp(prop_type[cur.marker],
+ rel_pos + 1,
+ result.text->len()))
+ result->extend({'end_pos': cur.end[1]})
+ return result
+enddef
+
+def ParseInlines(text: string, rel_pos: number = 0): dict<any>
+ var formatted = {
+ text: '',
+ props: []
+ }
+ var code_spans = GetCodeSpans(text)
+
+ var pos = 0
+ var seq = []
+ # search all emphasis
+ while pos < text->len()
+ var code_pos: list<number>
+ if code_spans->len() > 0
+ code_pos = [code_spans[0].start[0], code_spans[0].end[1]]
+ if pos >= code_pos[0]
+ pos = code_pos[1]
+ seq->add(code_spans->remove(0))
+ continue
+ endif
+ else
+ code_pos = [text->len(), text->len()]
+ endif
+ var delimiter = GetNextInlineDelimiter(text, pos, code_pos[0])
+ if delimiter->empty()
+ pos = code_pos[1]
+ continue
+ endif
+ if delimiter.right
+ var idx = seq->len() - 1
+ while idx >= 0
+ if delimiter.marker[0] != seq[idx].marker[0]
+ || seq[idx]->has_key('end')
+ idx -= 1
+ continue
+ endif
+ if delimiter.left || seq[idx].right
+ # check the sum rule
+ if (delimiter.marker->len() + seq[idx].marker->len()) % 3 == 0
+ && (delimiter.marker->len() % 3 > 0
+ || seq[idx].marker->len() % 3 > 0)
+ # not valid condition
+ idx -= 1
+ continue
+ endif
+ endif
+ var marker_len = min([delimiter.marker->len(),
+ seq[idx].marker->len(), 2])
+ if seq[idx].marker->len() > marker_len
+ var new_delim = {
+ marker: delimiter.marker[0]->repeat(marker_len),
+ start: [seq[idx].start[1] - marker_len, seq[idx].start[1]],
+ left: true,
+ right: false
+ }
+ seq[idx].marker = seq[idx].marker[: -1 - marker_len]
+ seq[idx].start[1] -= marker_len
+ seq[idx].right = false
+ idx += 1
+ seq->insert(new_delim, idx)
+ endif
+ seq[idx]->extend({
+ end: [delimiter.start[0],
+ delimiter.start[0] + marker_len]})
+ # close all overlapped emphasis spans not closed
+ for i in range(seq->len() - 1, idx + 1, -1)
+ if !seq[i]->has_key('end')
+ seq->remove(i)
+ endif
+ endfor
+ if delimiter.marker->len() > marker_len
+ delimiter.start[0] += marker_len
+ else
+ delimiter.left = false
+ break
+ endif
+ idx -= 1
+ endwhile
+ endif
+ if delimiter.left
+ seq->add(delimiter)
+ endif
+ pos = delimiter.start[1]
+ endwhile
+ while code_spans->len() > 0
+ seq->add(code_spans->remove(0))
+ endwhile
+ # remove all not closed delimiters
+ for i in range(seq->len() - 1, 0, -1)
+ if !seq[i]->has_key('end')
+ seq->remove(i)
+ endif
+ endfor
+
+ # compose final text
+ pos = 0
+ while seq->len() > 0
+ if pos < seq[0].start[0]
+ formatted.text ..= Unescape(text->strpart(pos, seq[0].start[0] - pos))
+ pos = seq[0].start[0]
+ endif
+ var inline = GetNextInlineBlock(text, seq,
+ rel_pos + formatted.text->len())
+ formatted.text ..= inline.text
+ formatted.props += inline.props
+ pos = inline.end_pos
+ endwhile
+ if pos < text->len()
+ formatted.text ..= Unescape(text->strpart(pos))
+ endif
+ return formatted
+enddef
+
+# new open container block
+def CreateContainerBlock(match: list<any>, start_lnum: number): dict<any>
+ if match[0][0] == '>'
+ return {
+ type: 'quote_block',
+ lnum: start_lnum,
+ indent: 0
+ }
+ else
+ return {
+ type: 'list_item',
+ lnum: start_lnum,
+ marker: $' {match[0]->matchstr("\\S\\+")} ',
+ indent: match[2]
+ }
+ endif
+enddef
+
+# new open leaf block
+def CreateLeafBlock(block_type: string, line: string, ...opt: list<any>): dict<any>
+ if block_type == 'fenced_code'
+ var token = line->matchlist(code_fence)
+ return {
+ type: block_type,
+ fence: token[1],
+ language: token[2],
+ text: []
+ }
+ elseif block_type == 'indented_code'
+ return {
+ type: block_type,
+ text: [line->matchstr(code_indent)]
+ }
+ elseif block_type == 'paragraph'
+ return {
+ type: block_type,
+ text: [line->matchstr(paragraph)]
+ }
+ elseif block_type == 'heading'
+ return {
+ type: block_type,
+ level: opt[0],
+ text: line
+ }
+ elseif block_type == 'table'
+ return {
+ type: block_type,
+ header: line,
+ delimiter: opt[0],
+ text: []
+ }
+ endif
+ return {}
+enddef
+
+def NeedBlankLine(prev: string, cur: string): bool
+ if prev == 'hr' || cur == 'hr'
+ return false
+ elseif prev == 'heading' || cur == 'heading'
+ return true
+ elseif prev == 'paragraph' && cur == 'paragraph'
+ return true
+ elseif prev != cur
+ return true
+ endif
+ return false
+enddef
+
+def SplitLine(line: dict<any>, indent: number = 0): list<dict<any>>
+ var lines: list<dict<any>> = []
+ var tokens: list<string> = line.text->split("\n", true)
+ if tokens->len() == 1
+ lines->add(line)
+ return lines
+ endif
+ var props: list<dict<any>> = line.props
+ for cur_text in tokens
+ var cur_props: list<dict<any>> = []
+ var next_props: list<dict<any>> = []
+ var length: number = cur_text->len()
+ for prop in props
+ if prop.col + prop.length - 1 <= length
+ cur_props->add(prop)
+ elseif prop.col > length
+ prop.col -= length + 1
+ next_props->add(prop)
+ else
+ var cur_length: number = length - prop.col + 1
+ cur_props->add({
+ type: prop.type,
+ col: prop.col,
+ length: cur_length
+ })
+ prop.col = 1
+ prop.length -= cur_length + 1
+ next_props->add(prop)
+ endif
+ endfor
+ lines->add({
+ text: cur_text,
+ props: cur_props
+ })
+ props = next_props
+ endfor
+ return lines
+enddef
+
+var last_block: string = ''
+
+def CloseBlocks(document: dict<list<any>>, blocks: list<dict<any>>, start: number = 0): void
+ if start >= blocks->len()
+ return
+ endif
+ var line: dict<any> = {
+ text: '',
+ props: []
+ }
+ if !document.content->empty() && NeedBlankLine(last_block, blocks[0].type)
+ document.content->add({text: '', props: []})
+ endif
+ last_block = blocks[0].type
+
+ for i in start->range()
+ if blocks[i]->has_key('marker')
+ if blocks[i].marker =~ '\S'
+ line.props->add(GetMarkerProp('list_item',
+ line.text->len() + 1,
+ blocks[i].marker->len()))
+ line.text ..= blocks[i].marker
+ blocks[i].marker = ' '->repeat(blocks[i].marker->len())
+ else
+ line.text ..= blocks[i].marker
+ endif
+ endif
+ endfor
+ for block in blocks->remove(start, -1)
+ if block.type =~ 'quote_block\|list_item'
+ if block->has_key('marker')
+ if block.marker =~ '\S'
+ line.props->add(GetMarkerProp('list_item',
+ line.text->len() + 1,
+ block.marker->len()))
+ line.text ..= block.marker
+ block.marker = ' '->repeat(block.marker->len())
+ else
+ line.text ..= block.marker
+ endif
+ endif
+ else
+ # leaf block
+ if block.type =~ '_code'
+ if block.type == 'indented_code'
+ while !block.text->empty() && block.text[0] !~ '\S'
+ block.text->remove(0)
+ endwhile
+ while !block.text->empty() && block.text[-1] !~ '\S'
+ block.text->remove(-1)
+ endwhile
+ endif
+ if !block.text->empty()
+ var indent = ' '->repeat(line.text->len())
+ var max_len = mapnew(block.text, (_, l) => l->len())->max()
+ var text = block.text->remove(0)
+ line.text ..= text
+ document.content->add(line)
+ var startline = document.content->len()
+ for l in block.text
+ document.content->add({text: indent .. l})
+ endfor
+ if block->has_key('language')
+ && !globpath(&rtp, $'syntax/{block.language}.vim')->empty()
+ document.syntax->add({lang: block.language,
+ start: $'\%{startline}l\%{indent->len() + 1}c',
+ end: $'\%{document.content->len()}l$'})
+ else
+ line.props->add(GetMarkerProp('code_block',
+ indent->len() + 1,
+ document.content->len(),
+ indent->len() + max_len + 1))
+ endif
+ endif
+ elseif block.type == 'heading'
+ line.props->add(GetMarkerProp('heading',
+ line.text->len() + 1,
+ block.text->len(),
+ block.level))
+ var format = ParseInlines(block.text, line.text->len())
+ line.text ..= format.text
+ line.props += format.props
+ document.content += SplitLine(line)
+ elseif block.type == 'table'
+ var indent = line.text
+ var head = block.header->split('\\\@1<!|')
+ var col1 = head->remove(0)
+ var format = ParseInlines(col1, line.text->len())
+ line.props->add(GetMarkerProp('table_header',
+ line.text->len() + 1,
+ format.text->len()))
+ line.text ..= format.text
+ line.props += format.props
+ for colx in head
+ format = ParseInlines(colx, line.text->len() + 1)
+ line.props->add(GetMarkerProp('table_sep', line.text->len() + 1, 1))
+ line.props->add(GetMarkerProp('table_header',
+ line.text->len() + 2,
+ format.text->len()))
+ line.text ..= $'|{format.text}'
+ line.props += format.props
+ endfor
+ document.content->add(line)
+ var data = {
+ text: indent .. block.delimiter,
+ props: [GetMarkerProp('table_sep',
+ indent->len() + 1,
+ block.delimiter->len())]
+ }
+ document.content->add(data)
+ for row in block.text
+ data = {
+ text: indent,
+ props: []
+ }
+ var cell = row->split('\\\@1<!|')
+ col1 = cell->remove(0)
+ format = ParseInlines(col1, data.text->len())
+ data.text ..= format.text
+ data.props += format.props
+ for colx in cell
+ format = ParseInlines(colx, data.text->len() + 1)
+ data.props->add(GetMarkerProp('table_sep',
+ data.text->len() + 1,
+ 1))
+ data.text ..= $'|{format.text}'
+ data.props += format.props
+ endfor
+ document.content->add(data)
+ endfor
+ elseif block.type == 'paragraph'
+ var indent = line.text->len()
+ var format = ParseInlines(block.text->join("\n")->substitute('\s\+$', '', ''), indent)
+ line.text ..= format.text
+ line.props += format.props
+ document.content += SplitLine(line, indent)
+ endif
+ endif
+ endfor
+enddef
+
+def ExpandTabs(line: string): string
+ var block_marker = line->matchstrpos($'^ \{{,3}}>[ \t]\+\|^[ \t]*\%({list_marker}\)\=[ \t]*')
+ if block_marker[0]->match('\t') < 0
+ return line
+ endif
+ var begin: string = ""
+ for char in block_marker[0]
+ if char == ' '
+ begin ..= ' '->repeat(4 - (begin->len() % 4))
+ else
+ begin ..= char
+ endif
+ endfor
+ return begin .. line[block_marker[2] :]
+enddef
+
+export def ParseMarkdown(data: list<string>, width: number = 80): dict<list<any>>
+ var document: dict<list<any>> = {content: [], syntax: []}
+ var open_blocks: list<dict<any>> = []
+
+ for l in data
+ var line: string = ExpandTabs(l)
+ var cur = 0
+
+ # for each open block check if current line continue it
+ while cur < open_blocks->len()
+ if open_blocks[cur].type == 'quote_block'
+ var marker = line->matchstrpos(block_quote)
+ if marker[1] == -1
+ break
+ endif
+ line = line->strpart(marker[2])
+ elseif open_blocks[cur].type == 'list_item'
+ var marker = line->matchstrpos($'^ \{{{open_blocks[cur].indent}}}')
+ if marker[1] == -1
+ break
+ endif
+ line = line->strpart(marker[2])
+ elseif open_blocks[cur].type == 'fenced_code'
+ if line =~ $'^ \{{,3}}{open_blocks[cur].fence}{open_blocks[cur].fence[0]}* *$'
+ CloseBlocks(document, open_blocks, cur)
+ else
+ open_blocks[cur].text->add(line)
+ endif
+ cur = -1
+ break
+ elseif open_blocks[cur].type == 'indented_code'
+ var marker = line->matchstrpos(code_indent)
+ if marker[1] >= 0
+ open_blocks[cur].text->add(marker[0])
+ cur = -1
+ endif
+ break
+ elseif open_blocks[cur].type == 'paragraph'
+ if line =~ setext_heading
+ var marker = line->matchstrpos(setext_heading)
+ open_blocks->add(CreateLeafBlock(
+ 'heading',
+ open_blocks->remove(cur).text->join("\n")->substitute('\s\+$', '', ''),
+ setext_heading_level[marker[0][0]]))
+ CloseBlocks(document, open_blocks, cur)
+ cur = -1
+ elseif open_blocks[cur].text->len() == 1
+ # may be a table
+ var marker = line->matchstr(table_delimiter)
+ if !marker->empty()
+ if open_blocks[cur].text[0]->split('\\\@1<!|')->len() == marker->split('|')->len()
+ open_blocks->add(CreateLeafBlock(
+ 'table',
+ open_blocks->remove(cur).text[0],
+ marker))
+ cur = -1
+ endif
+ endif
+ endif
+ break
+ endif
+ cur += 1
+ endwhile
+
+ if cur < 0
+ # the whole line is already consumed
+ continue
+ endif
+
+ # a thematic break close all previous blocks
+ if line =~ thematic_break
+ CloseBlocks(document, open_blocks)
+ if &g:encoding == 'utf-8'
+ document.content->add({text: "\u2500"->repeat(width)})
+ else
+ document.content->add({text: '-'->repeat(width)})
+ endif
+ last_block = 'hr'
+ continue
+ endif
+
+ # check for new container blocks
+ while true
+ var block = line->matchstrpos($'{block_quote}\|{list_item}')
+ if block[1] < 0
+ break
+ endif
+ # close unmatched blocks
+ CloseBlocks(document, open_blocks, cur)
+ # start a new block
+ open_blocks->add(CreateContainerBlock(block, document->len()))
+ cur = open_blocks->len()
+ line = line->strpart(block[2])
+ endwhile
+
+ # check for leaf block
+ if line =~ code_fence
+ CloseBlocks(document, open_blocks, cur)
+ open_blocks->add(CreateLeafBlock('fenced_code', line))
+ elseif line =~ blank_line
+ if open_blocks->empty()
+ continue
+ endif
+ if open_blocks[-1].type == 'paragraph'
+ CloseBlocks(document, open_blocks, min([cur, open_blocks->len() - 1]))
+ elseif open_blocks[-1].type == 'table'
+ CloseBlocks(document, open_blocks, open_blocks->len() - 1)
+ elseif open_blocks[-1].type =~ '_code'
+ open_blocks[-1].text->add(line)
+ endif
+ elseif line =~ code_indent
+ if open_blocks->empty()
+ open_blocks->add(CreateLeafBlock('indented_code', line))
+ elseif open_blocks[-1].type =~ '_code'
+ open_blocks[-1].text->add(line->matchstr(code_indent))
+ elseif open_blocks[-1].type == 'paragraph'
+ open_blocks[-1].text->add(line->matchstr(paragraph))
+ else
+ CloseBlocks(document, open_blocks, cur)
+ open_blocks->add(CreateLeafBlock('indented_code', line))
+ endif
+ elseif line =~ atx_heading
+ CloseBlocks(document, open_blocks, cur)
+ var token = line->matchlist(atx_heading)
+ open_blocks->add(CreateLeafBlock('heading', token[2], token[1]->len()))
+ CloseBlocks(document, open_blocks, cur)
+ elseif !open_blocks->empty()
+ if open_blocks[-1].type == 'table'
+ open_blocks[-1].text->add(line)
+ elseif open_blocks[-1].type == 'paragraph'
+ open_blocks[-1].text->add(line->matchstr(paragraph))
+ else
+ CloseBlocks(document, open_blocks, cur)
+ open_blocks->add(CreateLeafBlock('paragraph', line))
+ endif
+ else
+ open_blocks->add(CreateLeafBlock('paragraph', line))
+ endif
+ endfor
+
+ CloseBlocks(document, open_blocks)
+ return document
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/offset.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/offset.vim
new file mode 100644
index 0000000..699964e
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/offset.vim
@@ -0,0 +1,168 @@
+vim9script
+
+import './util.vim'
+
+# Functions for encoding and decoding the LSP position offsets. Language
+# servers support either UTF-8 or UTF-16 or UTF-32 position offsets. The
+# character related Vim functions use the UTF-32 position offset. The
+# encoding used is negotiated during the language server initialization.
+
+# Encode the UTF-32 character offset in the LSP position "pos" to the encoding
+# negotiated with the language server.
+#
+# Modifies in-place the UTF-32 offset in pos.character to a UTF-8 or UTF-16 or
+# UTF-32 offset.
+export def EncodePosition(lspserver: dict<any>, bnr: number, pos: dict<number>)
+ if has('patch-9.0.1629')
+ if lspserver.posEncoding == 32 || bnr <= 0
+ # LSP client plugin also uses utf-32 encoding
+ return
+ endif
+
+ :silent! bnr->bufload()
+ var text = bnr->getbufline(pos.line + 1)->get(0, '')
+ if text->empty()
+ return
+ endif
+
+ if lspserver.posEncoding == 16
+ pos.character = text->utf16idx(pos.character, true, true)
+ else
+ pos.character = text->byteidxcomp(pos.character)
+ endif
+ endif
+enddef
+
+# Decode the character offset in the LSP position "pos" using the encoding
+# negotiated with the language server to a UTF-32 offset.
+#
+# Modifies in-place the UTF-8 or UTF-16 or UTF-32 offset in pos.character to a
+# UTF-32 offset.
+export def DecodePosition(lspserver: dict<any>, bnr: number, pos: dict<number>)
+ if has('patch-9.0.1629')
+ if lspserver.posEncoding == 32 || bnr <= 0
+ # LSP client plugin also uses utf-32 encoding
+ return
+ endif
+
+ :silent! bnr->bufload()
+ var text = bnr->getbufline(pos.line + 1)->get(0, '')
+ # If the line is empty then don't decode the character position.
+ if text->empty()
+ return
+ endif
+
+ # If the character position is out-of-bounds, then don't decode the
+ # character position.
+ var textLen = 0
+ if lspserver.posEncoding == 16
+ textLen = text->strutf16len(true)
+ else
+ textLen = text->strlen()
+ endif
+
+ if pos.character > textLen
+ return
+ endif
+
+ if pos.character == textLen
+ pos.character = text->strchars()
+ else
+ if lspserver.posEncoding == 16
+ pos.character = text->charidx(pos.character, true, true)
+ else
+ pos.character = text->charidx(pos.character, true)
+ endif
+ endif
+ endif
+enddef
+
+# Encode the start and end UTF-32 character offsets in the LSP range "range"
+# to the encoding negotiated with the language server.
+#
+# Modifies in-place the UTF-32 offset in range.start.character and
+# range.end.character to a UTF-8 or UTF-16 or UTF-32 offset.
+export def EncodeRange(lspserver: dict<any>, bnr: number,
+ range: dict<dict<number>>)
+ if has('patch-9.0.1629')
+ if lspserver.posEncoding == 32
+ return
+ endif
+
+ EncodePosition(lspserver, bnr, range.start)
+ EncodePosition(lspserver, bnr, range.end)
+ endif
+enddef
+
+# Decode the start and end character offsets in the LSP range "range" to
+# UTF-32 offsets.
+#
+# Modifies in-place the offset value in range.start.character and
+# range.end.character to a UTF-32 offset.
+export def DecodeRange(lspserver: dict<any>, bnr: number,
+ range: dict<dict<number>>)
+ if has('patch-9.0.1629')
+ if lspserver.posEncoding == 32
+ return
+ endif
+
+ DecodePosition(lspserver, bnr, range.start)
+ DecodePosition(lspserver, bnr, range.end)
+ endif
+enddef
+
+# Encode the range in the LSP position "location" to the encoding negotiated
+# with the language server.
+#
+# Modifies in-place the UTF-32 offset in location.range to a UTF-8 or UTF-16
+# or UTF-32 offset.
+export def EncodeLocation(lspserver: dict<any>, location: dict<any>)
+ if has('patch-9.0.1629')
+ if lspserver.posEncoding == 32
+ return
+ endif
+
+ var bnr = 0
+ if location->has_key('targetUri')
+ # LocationLink
+ bnr = util.LspUriToBufnr(location.targetUri)
+ if bnr > 0
+ # We use only the "targetSelectionRange" item. The
+ # "originSelectionRange" and the "targetRange" items are not used.
+ lspserver.encodeRange(bnr, location.targetSelectionRange)
+ endif
+ else
+ # Location
+ bnr = util.LspUriToBufnr(location.uri)
+ if bnr > 0
+ lspserver.encodeRange(bnr, location.range)
+ endif
+ endif
+ endif
+enddef
+
+# Decode the range in the LSP location "location" to UTF-32.
+#
+# Modifies in-place the offset value in location.range to a UTF-32 offset.
+export def DecodeLocation(lspserver: dict<any>, location: dict<any>)
+ if has('patch-9.0.1629')
+ if lspserver.posEncoding == 32
+ return
+ endif
+
+ var bnr = 0
+ if location->has_key('targetUri')
+ # LocationLink
+ bnr = util.LspUriToBufnr(location.targetUri)
+ # We use only the "targetSelectionRange" item. The
+ # "originSelectionRange" and the "targetRange" items are not used.
+ lspserver.decodeRange(bnr, location.targetSelectionRange)
+ else
+ # Location
+ bnr = util.LspUriToBufnr(location.uri)
+ lspserver.decodeRange(bnr, location.range)
+ endif
+ endif
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/options.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/options.vim
new file mode 100644
index 0000000..50697a3
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/options.vim
@@ -0,0 +1,179 @@
+vim9script
+
+export const COMPLETIONMATCHER_CASE = 1
+export const COMPLETIONMATCHER_ICASE = 2
+export const COMPLETIONMATCHER_FUZZY = 3
+
+# LSP plugin options
+# User can override these by calling the OptionsSet() function.
+export var lspOptions: dict<any> = {
+ # Enable ale diagnostics support.
+ # If true, diagnostics will be sent to ale, which will be responsible for
+ # showing them.
+ aleSupport: false,
+
+ # In insert mode, complete the current symbol automatically
+ # Otherwise, use omni-completion
+ autoComplete: true,
+
+ # In normal mode, highlight the current symbol automatically
+ autoHighlight: false,
+
+ # Automatically highlight diagnostics messages from LSP server
+ autoHighlightDiags: true,
+
+ # Automatically populate the location list with new diagnostics
+ autoPopulateDiags: false,
+
+ # icase | fuzzy | case match for language servers that replies with a full
+ # list of completion items
+ completionMatcher: 'case',
+
+ # Due to a bug in the earlier versions of Vim, cannot use the
+ # COMPLETIONMATCHER_CASE constant here for initialization.
+ completionMatcherValue: 1,
+
+ # diagnostics signs options
+ diagSignErrorText: 'E>',
+ diagSignHintText: 'H>',
+ diagSignInfoText: 'I>',
+ diagSignWarningText: 'W>',
+
+ # In insert mode, echo the current symbol signature in the status line
+ # instead of showing it in a popup
+ echoSignature: false,
+
+ # hide disabled code actions
+ hideDisabledCodeActions: false,
+
+ # Highlight diagnostics inline
+ highlightDiagInline: true,
+
+ # Show the symbol documentation in the preview window instead of in a popup
+ hoverInPreview: false,
+
+ # Don't print message when a configured language server is missing.
+ ignoreMissingServer: false,
+
+ # Focus on the location list window after ":LspDiag show"
+ keepFocusInDiags: true,
+
+ # Focus on the location list window after LspShowReferences
+ keepFocusInReferences: true,
+
+ # If true, apply the LSP server supplied text edits after a completion.
+ # If a snippet plugin is going to apply the text edits, then set this to
+ # false to avoid applying the text edits twice.
+ completionTextEdit: true,
+
+ # Alignment of virtual diagnostic text, when showDiagWithVirtualText is true
+ # Allowed values: 'above' | 'below' | 'after' (default is 'above')
+ diagVirtualTextAlign: 'above',
+
+ # Wrapping of virtual diagnostic text, when showDiagWithVirtualText is true.
+ # Allowed valuse: 'default' | 'truncate' | 'wrap' (default is 'default')
+ diagVirtualTextWrap: 'default',
+
+ # Suppress adding a new line on completion selection with <CR>
+ noNewlineInCompletion: false,
+
+ # Omni-completion support. To keep backward compatibility, this option is
+ # set to null by default instead of false.
+ omniComplete: null,
+
+ # Open outline window on right side
+ outlineOnRight: false,
+
+ # Outline window size
+ outlineWinSize: 20,
+
+ # Enable semantic highlighting
+ semanticHighlight: false,
+
+ # Show diagnostic text in a balloon when the mouse is over the diagnostic
+ showDiagInBalloon: true,
+
+ # Make diagnostics show in a popup instead of echoing
+ showDiagInPopup: true,
+
+ # Suppress diagnostic hover from appearing when the mouse is over the line
+ # Show a diagnostic message on a status line
+ showDiagOnStatusLine: false,
+
+ # Show a diagnostic messages using signs
+ showDiagWithSign: true,
+
+ # Show a diagnostic messages with virtual text
+ showDiagWithVirtualText: false,
+
+ # enable inlay hints
+ showInlayHints: false,
+
+ # In insert mode, show the current symbol signature automatically
+ showSignature: true,
+
+ # enable snippet completion support
+ snippetSupport: false,
+
+ # enable SirVer/ultisnips completion support
+ ultisnipsSupport: false,
+
+ # add to autocomplition list current buffer words
+ useBufferCompletion: false,
+
+ # Use a floating menu to show the code action menu instead of asking for
+ # input
+ usePopupInCodeAction: false,
+
+ # ShowReferences in a quickfix list instead of a location list`
+ useQuickfixForLocations: false,
+
+ # enable hrsh7th/vim-vsnip completion support
+ vsnipSupport: false,
+
+ # Limit the time autocompletion searches for words in current buffer (in
+ # milliseconds)
+ bufferCompletionTimeout: 100,
+
+ # Enable support for custom completion kinds
+ customCompletionKinds: false,
+
+ # A dictionary with all completion kinds that you want to customize
+ completionKinds: {},
+
+ # Filter duplicate completion items
+ filterCompletionDuplicates: false,
+}
+
+# set the LSP plugin options from the user provided option values
+export def OptionsSet(opts: dict<any>)
+ lspOptions->extend(opts)
+ if !has('patch-9.0.0178')
+ lspOptions.showInlayHints = false
+ endif
+ if !has('patch-9.0.1157')
+ lspOptions.showDiagWithVirtualText = false
+ endif
+
+ # For faster comparison, convert the 'completionMatcher' option value from a
+ # string to a number.
+ if lspOptions.completionMatcher == 'icase'
+ lspOptions.completionMatcherValue = COMPLETIONMATCHER_ICASE
+ elseif lspOptions.completionMatcher == 'fuzzy'
+ lspOptions.completionMatcherValue = COMPLETIONMATCHER_FUZZY
+ else
+ lspOptions.completionMatcherValue = COMPLETIONMATCHER_CASE
+ endif
+
+ # Apply the changed options
+ if exists('#LspCmds#User#LspOptionsChanged')
+ :doautocmd <nomodeline> LspCmds User LspOptionsChanged
+ endif
+enddef
+
+# return a copy of the LSP plugin options
+export def OptionsGet(): dict<any>
+ return lspOptions->deepcopy()
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/outline.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/outline.vim
new file mode 100644
index 0000000..b414884
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/outline.vim
@@ -0,0 +1,304 @@
+vim9script
+
+import './util.vim'
+import './options.vim' as opt
+
+# jump to a symbol selected in the outline window
+def OutlineJumpToSymbol()
+ var lnum: number = line('.') - 1
+ if w:lspSymbols.lnumTable[lnum]->empty()
+ return
+ endif
+
+ var slnum: number = w:lspSymbols.lnumTable[lnum].lnum
+ var scol: number = w:lspSymbols.lnumTable[lnum].col
+ var fname: string = w:lspSymbols.filename
+
+ # Highlight the selected symbol
+ prop_remove({type: 'LspOutlineHighlight'})
+ var col: number = getline('.')->match('\S') + 1
+ prop_add(line('.'), col, {type: 'LspOutlineHighlight',
+ length: w:lspSymbols.lnumTable[lnum].name->len()})
+
+ # disable the outline window refresh
+ skipRefresh = true
+
+ # If the file is already opened in a window, jump to it. Otherwise open it
+ # in another window
+ var wid: number = fname->bufwinid()
+ if wid == -1
+ # Find a window showing a normal buffer and use it
+ for w in getwininfo()
+ if w.winid->getwinvar('&buftype')->empty()
+ wid = w.winid
+ wid->win_gotoid()
+ break
+ endif
+ endfor
+ if wid == -1
+ var symWinid: number = win_getid()
+ :rightbelow vnew
+ # retain the fixed symbol window width
+ win_execute(symWinid, 'vertical resize 20')
+ endif
+
+ exe $'edit {fname}'
+ else
+ wid->win_gotoid()
+ endif
+ [slnum, scol]->cursor()
+ skipRefresh = false
+enddef
+
+# Skip refreshing the outline window. Used to prevent recursive updates to the
+# outline window
+var skipRefresh: bool = false
+
+export def SkipOutlineRefresh(): bool
+ return skipRefresh
+enddef
+
+def AddSymbolText(bnr: number,
+ symbolTypeTable: dict<list<dict<any>>>,
+ pfx: string,
+ text: list<string>,
+ lnumMap: list<dict<any>>,
+ children: bool)
+ var prefix: string = pfx .. ' '
+ for [symType, symbols] in symbolTypeTable->items()
+ if !children
+ # Add an empty line for the top level symbol types. For types in the
+ # children symbols, don't add the empty line.
+ text->extend([''])
+ lnumMap->extend([{}])
+ endif
+ if children
+ text->extend([prefix .. symType])
+ prefix ..= ' '
+ else
+ text->extend([symType])
+ endif
+ lnumMap->extend([{}])
+ for s in symbols
+ text->add(prefix .. s.name)
+ # remember the line number for the symbol
+ var s_start = s.range.start
+ var start_col: number = util.GetLineByteFromPos(bnr, s_start) + 1
+ lnumMap->add({name: s.name, lnum: s_start.line + 1,
+ col: start_col})
+ s.outlineLine = lnumMap->len()
+ if s->has_key('children') && !s.children->empty()
+ AddSymbolText(bnr, s.children, prefix, text, lnumMap, true)
+ endif
+ endfor
+ endfor
+enddef
+
+# update the symbols displayed in the outline window
+export def UpdateOutlineWindow(fname: string,
+ symbolTypeTable: dict<list<dict<any>>>,
+ symbolLineTable: list<dict<any>>)
+ var wid: number = bufwinid('LSP-Outline')
+ if wid == -1
+ return
+ endif
+
+ # stop refreshing the outline window recursively
+ skipRefresh = true
+
+ var prevWinID: number = win_getid()
+ wid->win_gotoid()
+
+ # if the file displayed in the outline window is same as the new file, then
+ # save and restore the cursor position
+ var symbols = wid->getwinvar('lspSymbols', {})
+ var saveCursor: list<number> = []
+ if !symbols->empty() && symbols.filename == fname
+ saveCursor = getcurpos()
+ endif
+
+ :setlocal modifiable
+ :silent! :%d _
+ setline(1, ['# LSP Outline View',
+ $'# {fname->fnamemodify(":t")} ({fname->fnamemodify(":h")})'])
+
+ # First two lines in the buffer display comment information
+ var lnumMap: list<dict<any>> = [{}, {}]
+ var text: list<string> = []
+ AddSymbolText(fname->bufnr(), symbolTypeTable, '', text, lnumMap, false)
+ text->append('$')
+ w:lspSymbols = {filename: fname, lnumTable: lnumMap,
+ symbolsByLine: symbolLineTable}
+ :setlocal nomodifiable
+
+ if !saveCursor->empty()
+ saveCursor->setpos('.')
+ endif
+
+ prevWinID->win_gotoid()
+
+ # Highlight the current symbol
+ OutlineHighlightCurrentSymbol()
+
+ # re-enable refreshing the outline window
+ skipRefresh = false
+enddef
+
+def OutlineHighlightCurrentSymbol()
+ var fname: string = expand('%')->fnamemodify(':p')
+ if fname->empty() || &filetype->empty()
+ return
+ endif
+
+ var wid: number = bufwinid('LSP-Outline')
+ if wid == -1
+ return
+ endif
+
+ # Check whether the symbols for this file are displayed in the outline
+ # window
+ var lspSymbols = wid->getwinvar('lspSymbols', {})
+ if lspSymbols->empty() || lspSymbols.filename != fname
+ return
+ endif
+
+ var symbolTable: list<dict<any>> = lspSymbols.symbolsByLine
+
+ # line number to locate the symbol
+ var lnum: number = line('.')
+
+ # Find the symbol for the current line number (binary search)
+ var left: number = 0
+ var right: number = symbolTable->len() - 1
+ var mid: number
+ while left <= right
+ mid = (left + right) / 2
+ var r = symbolTable[mid].range
+ if lnum >= (r.start.line + 1) && lnum <= (r.end.line + 1)
+ break
+ endif
+ if lnum > (r.start.line + 1)
+ left = mid + 1
+ else
+ right = mid - 1
+ endif
+ endwhile
+
+ # clear the highlighting in the outline window
+ var bnr: number = wid->winbufnr()
+ prop_remove({bufnr: bnr, type: 'LspOutlineHighlight'})
+
+ if left > right
+ # symbol not found
+ return
+ endif
+
+ # Highlight the selected symbol
+ var col: number =
+ bnr->getbufline(symbolTable[mid].outlineLine)->get(0, '')->match('\S') + 1
+ prop_add(symbolTable[mid].outlineLine, col,
+ {bufnr: bnr, type: 'LspOutlineHighlight',
+ length: symbolTable[mid].name->len()})
+
+ # if the line is not visible, then scroll the outline window to make the
+ # line visible
+ var wininfo = wid->getwininfo()
+ if symbolTable[mid].outlineLine < wininfo[0].topline
+ || symbolTable[mid].outlineLine > wininfo[0].botline
+ var cmd: string = $'call cursor({symbolTable[mid].outlineLine}, 1) | normal z.'
+ win_execute(wid, cmd)
+ endif
+enddef
+
+# when the outline window is closed, do the cleanup
+def OutlineCleanup()
+ # Remove the outline autocommands
+ :silent! autocmd_delete([{group: 'LSPOutline'}])
+
+ :silent! syntax clear LSPTitle
+enddef
+
+# open the symbol outline window
+export def OpenOutlineWindow(cmdmods: string, winsize: number)
+ var wid: number = bufwinid('LSP-Outline')
+ if wid != -1
+ return
+ endif
+
+ var prevWinID: number = win_getid()
+
+ var mods = cmdmods
+ if mods->empty()
+ if opt.lspOptions.outlineOnRight
+ mods = ':vert :botright'
+ else
+ mods = ':vert :topleft'
+ endif
+ endif
+
+ var size = winsize
+ if size == 0
+ size = opt.lspOptions.outlineWinSize
+ endif
+
+ execute $'{mods} :{size}new LSP-Outline'
+ :setlocal modifiable
+ :setlocal noreadonly
+ :silent! :%d _
+ :setlocal buftype=nofile
+ :setlocal bufhidden=delete
+ :setlocal noswapfile nobuflisted
+ :setlocal nonumber norelativenumber fdc=0 nowrap winfixheight winfixwidth
+ :setlocal shiftwidth=2
+ :setlocal foldenable
+ :setlocal foldcolumn=4
+ :setlocal foldlevel=4
+ :setlocal foldmethod=indent
+ setline(1, ['# File Outline'])
+ :nnoremap <silent> <buffer> q :quit<CR>
+ :nnoremap <silent> <buffer> <CR> :call <SID>OutlineJumpToSymbol()<CR>
+ :setlocal nomodifiable
+
+ # highlight all the symbol types
+ :syntax keyword LSPTitle File Module Namespace Package Class Method Property
+ :syntax keyword LSPTitle Field Constructor Enum Interface Function Variable
+ :syntax keyword LSPTitle Constant String Number Boolean Array Object Key Null
+ :syntax keyword LSPTitle EnumMember Struct Event Operator TypeParameter
+
+ if str2nr(&t_Co) > 2
+ :highlight clear LSPTitle
+ :highlight default link LSPTitle Title
+ endif
+
+ prop_type_add('LspOutlineHighlight', {bufnr: bufnr(), highlight: 'Search', override: true})
+
+ try
+ autocmd_delete([{group: 'LSPOutline', event: '*'}])
+ catch /E367:/
+ endtry
+ var acmds: list<dict<any>>
+
+ # Refresh or add the symbols in a buffer to the outline window
+ acmds->add({event: 'BufEnter',
+ group: 'LSPOutline',
+ pattern: '*',
+ cmd: 'call g:LspRequestDocSymbols()'})
+
+ # when the outline window is closed, do the cleanup
+ acmds->add({event: 'BufUnload',
+ group: 'LSPOutline',
+ pattern: 'LSP-Outline',
+ cmd: 'OutlineCleanup()'})
+
+ # Highlight the current symbol when the cursor is not moved for sometime
+ acmds->add({event: 'CursorHold',
+ group: 'LSPOutline',
+ pattern: '*',
+ cmd: 'OutlineHighlightCurrentSymbol()'})
+
+ autocmd_add(acmds)
+
+ prevWinID->win_gotoid()
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/selection.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/selection.vim
new file mode 100644
index 0000000..1ef289b
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/selection.vim
@@ -0,0 +1,107 @@
+vim9script
+
+# Functions related to handling LSP range selection.
+
+import './util.vim'
+
+# Visually (character-wise) select the text in a range
+def SelectText(bnr: number, range: dict<dict<number>>)
+ var rstart = range.start
+ var rend = range.end
+ var start_col: number = util.GetLineByteFromPos(bnr, rstart) + 1
+ var end_col: number = util.GetLineByteFromPos(bnr, rend)
+
+ :normal! v"_y
+ setcharpos("'<", [0, rstart.line + 1, start_col, 0])
+ setcharpos("'>", [0, rend.line + 1, end_col, 0])
+ :normal! gv
+enddef
+
+# Process the range selection reply from LSP server and start a new selection
+export def SelectionStart(lspserver: dict<any>, sel: list<dict<any>>)
+ if sel->empty()
+ return
+ endif
+
+ var bnr: number = bufnr()
+
+ # save the reply for expanding or shrinking the selected text.
+ lspserver.selection = {bnr: bnr, selRange: sel[0], index: 0}
+
+ SelectText(bnr, sel[0].range)
+enddef
+
+# Locate the range in the LSP reply at a specified level
+def GetSelRangeAtLevel(selRange: dict<any>, level: number): dict<any>
+ var r: dict<any> = selRange
+ var idx: number = 0
+
+ while idx != level
+ if !r->has_key('parent')
+ break
+ endif
+ r = r.parent
+ idx += 1
+ endwhile
+
+ return r
+enddef
+
+# Returns true if the current visual selection matches a range in the
+# selection reply from LSP.
+def SelectionFromLSP(range: dict<any>, startpos: list<number>, endpos: list<number>): bool
+ var rstart = range.start
+ var rend = range.end
+ return startpos[1] == rstart.line + 1
+ && endpos[1] == rend.line + 1
+ && startpos[2] == rstart.character + 1
+ && endpos[2] == rend.character
+enddef
+
+# Expand or Shrink the current selection or start a new one.
+export def SelectionModify(lspserver: dict<any>, expand: bool)
+ var fname: string = @%
+ var bnr: number = bufnr()
+
+ if mode() == 'v' && !lspserver.selection->empty()
+ && lspserver.selection.bnr == bnr
+ && !lspserver.selection->empty()
+ # Already in characterwise visual mode and the previous LSP selection
+ # reply for this buffer is available. Modify the current selection.
+
+ var selRange: dict<any> = lspserver.selection.selRange
+ var startpos: list<number> = getcharpos('v')
+ var endpos: list<number> = getcharpos('.')
+ var idx: number = lspserver.selection.index
+
+ # Locate the range in the LSP reply for the current selection
+ selRange = GetSelRangeAtLevel(selRange, lspserver.selection.index)
+
+ # If the current selection is present in the LSP reply, then modify the
+ # selection
+ if SelectionFromLSP(selRange.range, startpos, endpos)
+ if expand
+ # expand the selection
+ if selRange->has_key('parent')
+ selRange = selRange.parent
+ lspserver.selection.index = idx + 1
+ endif
+ else
+ # shrink the selection
+ if idx > 0
+ idx -= 1
+ selRange = GetSelRangeAtLevel(lspserver.selection.selRange, idx)
+ lspserver.selection.index = idx
+ endif
+ endif
+
+ SelectText(bnr, selRange.range)
+ return
+ endif
+ endif
+
+ # Start a new selection
+ lspserver.selectionRange(fname)
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/semantichighlight.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/semantichighlight.vim
new file mode 100644
index 0000000..243aea9
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/semantichighlight.vim
@@ -0,0 +1,264 @@
+vim9script
+
+# LSP semantic highlighting functions
+
+import './offset.vim'
+import './options.vim' as opt
+import './buffer.vim' as buf
+
+# Map token type names to higlight group/text property type names
+var TokenTypeMap: dict<string> = {
+ 'namespace': 'LspSemanticNamespace',
+ 'type': 'LspSemanticType',
+ 'class': 'LspSemanticClass',
+ 'enum': 'LspSemanticEnum',
+ 'interface': 'LspSemanticInterface',
+ 'struct': 'LspSemanticStruct',
+ 'typeParameter': 'LspSemanticTypeParameter',
+ 'parameter': 'LspSemanticParameter',
+ 'variable': 'LspSemanticVariable',
+ 'property': 'LspSemanticProperty',
+ 'enumMember': 'LspSemanticEnumMember',
+ 'event': 'LspSemanticEvent',
+ 'function': 'LspSemanticFunction',
+ 'method': 'LspSemanticMethod',
+ 'macro': 'LspSemanticMacro',
+ 'keyword': 'LspSemanticKeyword',
+ 'modifier': 'LspSemanticModifier',
+ 'comment': 'LspSemanticComment',
+ 'string': 'LspSemanticString',
+ 'number': 'LspSemanticNumber',
+ 'regexp': 'LspSemanticRegexp',
+ 'operator': 'LspSemanticOperator',
+ 'decorator': 'LspSemanticDecorator'
+}
+
+export def InitOnce()
+ # Define the default semantic token type highlight groups
+ hlset([
+ {name: 'LspSemanticNamespace', default: true, linksto: 'Type'},
+ {name: 'LspSemanticType', default: true, linksto: 'Type'},
+ {name: 'LspSemanticClass', default: true, linksto: 'Type'},
+ {name: 'LspSemanticEnum', default: true, linksto: 'Type'},
+ {name: 'LspSemanticInterface', default: true, linksto: 'TypeDef'},
+ {name: 'LspSemanticStruct', default: true, linksto: 'Type'},
+ {name: 'LspSemanticTypeParameter', default: true, linksto: 'Type'},
+ {name: 'LspSemanticParameter', default: true, linksto: 'Identifier'},
+ {name: 'LspSemanticVariable', default: true, linksto: 'Identifier'},
+ {name: 'LspSemanticProperty', default: true, linksto: 'Identifier'},
+ {name: 'LspSemanticEnumMember', default: true, linksto: 'Constant'},
+ {name: 'LspSemanticEvent', default: true, linksto: 'Identifier'},
+ {name: 'LspSemanticFunction', default: true, linksto: 'Function'},
+ {name: 'LspSemanticMethod', default: true, linksto: 'Function'},
+ {name: 'LspSemanticMacro', default: true, linksto: 'Macro'},
+ {name: 'LspSemanticKeyword', default: true, linksto: 'Keyword'},
+ {name: 'LspSemanticModifier', default: true, linksto: 'Type'},
+ {name: 'LspSemanticComment', default: true, linksto: 'Comment'},
+ {name: 'LspSemanticString', default: true, linksto: 'String'},
+ {name: 'LspSemanticNumber', default: true, linksto: 'Number'},
+ {name: 'LspSemanticRegexp', default: true, linksto: 'String'},
+ {name: 'LspSemanticOperator', default: true, linksto: 'Operator'},
+ {name: 'LspSemanticDecorator', default: true, linksto: 'Macro'}
+ ])
+
+ for hlName in TokenTypeMap->values()
+ prop_type_add(hlName, {highlight: hlName, combine: true})
+ endfor
+enddef
+
+def ParseSemanticTokenMods(lspserverTokenMods: list<string>, tokenMods: number): string
+ var n = tokenMods
+ var tokenMod: number
+ var str = ''
+
+ while n > 0
+ tokenMod = float2nr(log10(and(n, invert(n - 1))) / log10(2))
+ str = $'{str}{lspserverTokenMods[tokenMod]},'
+ n = and(n, n - 1)
+ endwhile
+
+ return str
+enddef
+
+# Apply the edit operations in a semantic tokens delta update message
+# (SemanticTokensDelta) from the language server.
+#
+# The previous list of tokens are stored in the buffer-local
+# LspSemanticTokensData variable. After applying the edits in
+# semTokens.edits, the new set of tokens are returned in semTokens.data.
+def ApplySemanticTokenEdits(bnr: number, semTokens: dict<any>)
+ if semTokens.edits->empty()
+ return
+ endif
+
+ # Need to sort the edits and apply the last edit first.
+ semTokens.edits->sort((a: dict<any>, b: dict<any>) => a.start - b.start)
+
+ # TODO: Remove this code
+ # var d = bnr->getbufvar('LspSemanticTokensData', [])
+ # for e in semTokens.edits
+ # var insertData = e->get('data', [])
+ # d = (e.start > 0 ? d[: e.start - 1] : []) + insertData +
+ # d[e.start + e.deleteCount :]
+ # endfor
+ # semTokens.data = d
+
+ var oldTokens = bnr->getbufvar('LspSemanticTokensData', [])
+ var newTokens = []
+ var idx = 0
+ for e in semTokens.edits
+ if e.start > 0
+ newTokens->extend(oldTokens[idx : e.start - 1])
+ endif
+ newTokens->extend(e->get('data', []))
+ idx = e.start + e.deleteCount
+ endfor
+ newTokens->extend(oldTokens[idx : ])
+ semTokens.data = newTokens
+enddef
+
+# Process a list of semantic tokens and return the corresponding text
+# properties for highlighting.
+def ProcessSemanticTokens(lspserver: dict<any>, bnr: number, tokens: list<number>): dict<list<list<number>>>
+ var props: dict<list<list<number>>> = {}
+ var tokenLine: number = 0
+ var startChar: number = 0
+ var length: number = 0
+ var tokenType: number = 0
+ var tokenMods: number = 0
+ var prevTokenLine = 0
+ var lnum = 1
+ var charIdx = 0
+
+ var lspserverTokenTypes: list<string> =
+ lspserver.semanticTokensLegend.tokenTypes
+ var lspserverTokenMods: list<string> =
+ lspserver.semanticTokensLegend.tokenModifiers
+
+ # Each semantic token uses 5 items in the tokens List
+ var i = 0
+ while i < tokens->len()
+ tokenLine = tokens[i]
+ # tokenLine is relative to the previous token line number
+ lnum += tokenLine
+ if prevTokenLine != lnum
+ # this token is on a different line from the previous token
+ charIdx = 0
+ prevTokenLine = lnum
+ endif
+ startChar = tokens[i + 1]
+ charIdx += startChar
+ length = tokens[i + 2]
+ tokenType = tokens[i + 3]
+ tokenMods = tokens[i + 4]
+
+ var typeStr = lspserverTokenTypes[tokenType]
+ var modStr = ParseSemanticTokenMods(lspserverTokenMods, tokenMods)
+
+ # Decode the semantic token line number, column number and length to
+ # UTF-32 encoding.
+ var r = {
+ start: {
+ line: lnum - 1,
+ character: charIdx
+ },
+ end: {
+ line: lnum - 1,
+ character: charIdx + length
+ }
+ }
+ offset.DecodeRange(lspserver, bnr, r)
+
+ if !props->has_key(typeStr)
+ props[typeStr] = []
+ endif
+ props[typeStr]->add([
+ lnum, r.start.character + 1,
+ lnum, r.end.character + 1
+ ])
+
+ i += 5
+ endwhile
+
+ return props
+enddef
+
+# Parse the semantic highlight reply from the language server and update the
+# text properties
+export def UpdateTokens(lspserver: dict<any>, bnr: number, semTokens: dict<any>)
+
+ if semTokens->has_key('edits')
+ # Delta semantic update. Need to sort the edits and apply the last edit
+ # first.
+ ApplySemanticTokenEdits(bnr, semTokens)
+ endif
+
+ # Cache the semantic tokens in a buffer-local variable, it will be used
+ # later for a delta update.
+ setbufvar(bnr, 'LspSemanticResultId', semTokens->get('resultId', ''))
+ if !semTokens->has_key('data')
+ return
+ endif
+ setbufvar(bnr, 'LspSemanticTokensData', semTokens.data)
+
+ var props: dict<list<list<number>>>
+ props = ProcessSemanticTokens(lspserver, bnr, semTokens.data)
+
+ # First clear all the previous text properties
+ if has('patch-9.0.0233')
+ prop_remove({types: TokenTypeMap->values(), bufnr: bnr, all: true})
+ else
+ for propName in TokenTypeMap->values()
+ prop_remove({type: propName, bufnr: bnr, all: true})
+ endfor
+ endif
+
+ if props->empty()
+ return
+ endif
+
+ # Apply the new text properties
+ for tokenType in TokenTypeMap->keys()
+ if props->has_key(tokenType)
+ prop_add_list({bufnr: bnr, type: TokenTypeMap[tokenType]},
+ props[tokenType])
+ endif
+ endfor
+enddef
+
+# Update the semantic highlighting for buffer "bnr"
+def LspUpdateSemanticHighlight(bnr: number)
+ var lspserver: dict<any> = buf.BufLspServerGet(bnr, 'semanticTokens')
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.semanticHighlightUpdate(bnr)
+enddef
+
+# Initialize the semantic highlighting for the buffer 'bnr'
+export def BufferInit(lspserver: dict<any>, bnr: number)
+ if !opt.lspOptions.semanticHighlight || !lspserver.isSemanticTokensProvider
+ # no support for semantic highlighting
+ return
+ endif
+
+ # Highlight all the semantic tokens
+ LspUpdateSemanticHighlight(bnr)
+
+ # buffer-local autocmds for semantic highlighting
+ var acmds: list<dict<any>> = []
+
+ acmds->add({bufnr: bnr,
+ event: 'TextChanged',
+ group: 'LSPBufferAutocmds',
+ cmd: $'LspUpdateSemanticHighlight({bnr})'})
+ acmds->add({bufnr: bnr,
+ event: 'BufUnload',
+ group: 'LSPBufferAutocmds',
+ cmd: $"b:LspSemanticTokensData = [] | b:LspSemanticResultId = ''"})
+
+ autocmd_add(acmds)
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/signature.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/signature.vim
new file mode 100644
index 0000000..ca2bed4
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/signature.vim
@@ -0,0 +1,145 @@
+vim9script
+
+# Functions related to handling LSP symbol signature help.
+
+import './options.vim' as opt
+import './util.vim'
+import './buffer.vim' as buf
+
+# close the signature popup window
+def CloseSignaturePopup(lspserver: dict<any>)
+ if lspserver.signaturePopup != -1
+ lspserver.signaturePopup->popup_close()
+ endif
+ lspserver.signaturePopup = -1
+enddef
+
+def CloseCurBufSignaturePopup()
+ var lspserver: dict<any> = buf.CurbufGetServer('signatureHelp')
+ if lspserver->empty()
+ return
+ endif
+
+ CloseSignaturePopup(lspserver)
+enddef
+
+# Show the signature using "textDocument/signatureHelp" LSP method
+# Invoked from an insert-mode mapping, so return an empty string.
+def g:LspShowSignature(): string
+ var lspserver: dict<any> = buf.CurbufGetServerChecked('signatureHelp')
+ if lspserver->empty()
+ return ''
+ endif
+
+ # first send all the changes in the current buffer to the LSP server
+ listener_flush()
+ lspserver.showSignature()
+ return ''
+enddef
+
+export def InitOnce()
+ hlset([{name: 'LspSigActiveParameter', default: true, linksto: 'LineNr'}])
+enddef
+
+# Initialize the signature triggers for the current buffer
+export def BufferInit(lspserver: dict<any>)
+ if !lspserver.isSignatureHelpProvider
+ || !lspserver.caps.signatureHelpProvider->has_key('triggerCharacters')
+ # no support for signature help
+ return
+ endif
+
+ if !opt.lspOptions.showSignature
+ || !lspserver.featureEnabled('signatureHelp')
+ # Show signature support is disabled
+ return
+ endif
+
+ # map characters that trigger signature help
+ for ch in lspserver.caps.signatureHelpProvider.triggerCharacters
+ exe $"inoremap <buffer> <silent> {ch} {ch}<C-R>=g:LspShowSignature()<CR>"
+ endfor
+
+ # close the signature popup when leaving insert mode
+ autocmd_add([{bufnr: bufnr(),
+ event: 'InsertLeave',
+ cmd: 'CloseCurBufSignaturePopup()'}])
+enddef
+
+# process the 'textDocument/signatureHelp' reply from the LSP server and
+# display the symbol signature help.
+# Result: SignatureHelp | null
+export def SignatureHelp(lspserver: dict<any>, sighelp: any): void
+ if sighelp->empty()
+ CloseSignaturePopup(lspserver)
+ return
+ endif
+
+ if sighelp.signatures->len() <= 0
+ CloseSignaturePopup(lspserver)
+ return
+ endif
+
+ var sigidx: number = 0
+ if sighelp->has_key('activeSignature')
+ sigidx = sighelp.activeSignature
+ endif
+
+ var sig: dict<any> = sighelp.signatures[sigidx]
+ var text: string = sig.label
+ var hllen: number = 0
+ var startcol: number = 0
+ if sig->has_key('parameters') && sighelp->has_key('activeParameter')
+ var params: list<dict<any>> = sig.parameters
+ var params_len: number = params->len()
+ var activeParam: number = sighelp.activeParameter
+ if params_len > 0 && activeParam < params_len
+ var paramInfo: dict<any> = params[activeParam]
+ var label: any = paramInfo.label
+ if label->type() == v:t_string
+ # label string
+ var label_str: string = label
+ hllen = label_str->len()
+ startcol = text->stridx(label_str)
+ else
+ # [inclusive start offset, exclusive end offset]
+ var label_offset: list<number> = params[activeParam].label
+ var start_offset: number = label_offset[0]
+ var end_offset: number = label_offset[1]
+
+ if has('patch-9.0.1629')
+ # Convert UTF-16 offsets
+ startcol = text->byteidx(start_offset, true)
+ var endcol: number = text->byteidx(end_offset, true)
+ hllen = endcol - startcol
+ else
+ startcol = start_offset
+ hllen = end_offset - start_offset
+ endif
+ endif
+ endif
+ endif
+
+ if opt.lspOptions.echoSignature
+ :echon "\r\r"
+ :echon ''
+ :echon text->strpart(0, startcol)
+ :echoh LspSigActiveParameter
+ :echon text->strpart(startcol, hllen)
+ :echoh None
+ :echon text->strpart(startcol + hllen)
+ else
+ # Close the previous signature popup and open a new one
+ lspserver.signaturePopup->popup_close()
+
+ var popupID = text->popup_atcursor({padding: [0, 1, 0, 1], moved: [col('.') - 1, 9999999]})
+ var bnr: number = popupID->winbufnr()
+ prop_type_add('signature', {bufnr: bnr, highlight: 'LspSigActiveParameter'})
+ if hllen > 0
+ prop_add(1, startcol + 1, {bufnr: bnr, length: hllen, type: 'signature'})
+ endif
+ lspserver.signaturePopup = popupID
+ endif
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/snippet.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/snippet.vim
new file mode 100644
index 0000000..c03b49d
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/snippet.vim
@@ -0,0 +1,82 @@
+vim9script
+
+# Snippet support
+
+# Integration with the UltiSnips plugin
+export def CompletionUltiSnips(prefix: string, items: list<dict<any>>)
+ call UltiSnips#SnippetsInCurrentScope(1)
+ for key in matchfuzzy(g:current_ulti_dict_info->keys(), prefix)
+ var item = g:current_ulti_dict_info[key]
+ var parts = split(item.location, ':')
+ var txt = parts[0]->readfile()[parts[1]->str2nr() : parts[1]->str2nr() + 20]
+ var restxt = item.description .. "\n\n"
+ for line in txt
+ if line->empty() || line[0 : 6] == "snippet"
+ break
+ else
+ restxt = restxt .. line .. "\n"
+ endif
+ endfor
+ items->add({
+ label: key,
+ data: {
+ entryNames: [key],
+ },
+ kind: 15,
+ documentation: restxt,
+ })
+ endfor
+enddef
+
+# Integration with the vim-vsnip plugin
+export def CompletionVsnip(items: list<dict<any>>)
+ def Pattern(abbr: string): string
+ var chars = escape(abbr, '\/?')->split('\zs')
+ var chars_pattern = '\%(\V' .. chars->join('\m\|\V') .. '\m\)'
+ var separator = chars[0] =~ '\a' ? '\<' : ''
+ return $'{separator}\V{chars[0]}\m{chars_pattern}*$'
+ enddef
+
+ if charcol('.') == 1
+ return
+ endif
+ var starttext = getline('.')->slice(0, charcol('.') - 1)
+ for item in vsnip#get_complete_items(bufnr())
+ var match = starttext->matchstrpos(Pattern(item.abbr))
+ if match[0] != ''
+ var user_data = item.user_data->json_decode()
+ var documentation = []
+ for line in vsnip#to_string(user_data.vsnip.snippet)->split("\n")
+ documentation->add(line)
+ endfor
+ items->add({
+ label: item.abbr,
+ filterText: item.word,
+ insertTextFormat: 2,
+ textEdit: {
+ newText: user_data.vsnip.snippet->join("\n"),
+ range: {
+ start: {
+ line: line('.'),
+ character: match[1],
+ },
+ ['end']: {
+ line: line('.'),
+ character: match[2],
+ },
+ },
+ },
+ data: {
+ entryNames: [item.word],
+ },
+ kind: 15,
+ documentation: {
+ kind: 'markdown',
+ value: documentation->join("\n"),
+ },
+ })
+ endif
+ endfor
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/symbol.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/symbol.vim
new file mode 100644
index 0000000..bcd547a
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/symbol.vim
@@ -0,0 +1,1014 @@
+vim9script
+
+# Functions for dealing with symbols.
+# - LSP symbol menu and for searching symbols across the workspace.
+# - show locations
+# - jump to a symbol definition, declaration, type definition or
+# implementation
+
+import './options.vim' as opt
+import './util.vim'
+import './outline.vim'
+
+# Initialize the highlight group and the text property type used for
+# document symbol search
+export def InitOnce()
+ # Use a high priority value to override other highlights in the line
+ hlset([
+ {name: 'LspSymbolName', default: true, linksto: 'Search'},
+ {name: 'LspSymbolRange', default: true, linksto: 'Visual'}
+ ])
+ prop_type_add('LspSymbolNameProp', {highlight: 'LspSymbolName',
+ combine: false,
+ override: true,
+ priority: 201})
+ prop_type_add('LspSymbolRangeProp', {highlight: 'LspSymbolRange',
+ combine: false,
+ override: true,
+ priority: 200})
+enddef
+
+# Handle keys pressed when the workspace symbol popup menu is displayed
+def FilterSymbols(lspserver: dict<any>, popupID: number, key: string): bool
+ var key_handled: bool = false
+ var update_popup: bool = false
+ var query: string = lspserver.workspaceSymbolQuery
+
+ if key == "\<BS>" || key == "\<C-H>"
+ # Erase one character from the filter text
+ if query->len() >= 1
+ query = query[: -2]
+ update_popup = true
+ endif
+ key_handled = true
+ elseif key == "\<C-U>"
+ # clear the filter text
+ query = ''
+ update_popup = true
+ key_handled = true
+ elseif key == "\<C-F>"
+ || key == "\<C-B>"
+ || key == "\<PageUp>"
+ || key == "\<PageDown>"
+ || key == "\<C-Home>"
+ || key == "\<C-End>"
+ || key == "\<C-N>"
+ || key == "\<C-P>"
+ # scroll the popup window
+ var cmd: string = 'normal! ' .. (key == "\<C-N>" ? 'j' : key == "\<C-P>" ? 'k' : key)
+ win_execute(popupID, cmd)
+ key_handled = true
+ elseif key == "\<Up>" || key == "\<Down>"
+ # Use native Vim handling for these keys
+ key_handled = false
+ elseif key =~ '^\f$' || key == "\<Space>"
+ # Filter the names based on the typed key and keys typed before
+ query ..= key
+ update_popup = true
+ key_handled = true
+ endif
+
+ if update_popup
+ # Update the popup with the new list of symbol names
+ popupID->popup_settext('')
+ if query != ''
+ lspserver.workspaceQuery(query, false)
+ else
+ []->setwinvar(popupID, 'LspSymbolTable')
+ endif
+ :echo $'Symbol: {query}'
+ endif
+
+ # Update the workspace symbol query string
+ lspserver.workspaceSymbolQuery = query
+
+ if key_handled
+ return true
+ endif
+
+ return popupID->popup_filter_menu(key)
+enddef
+
+# Jump to the location of a symbol selected in the popup menu
+def JumpToWorkspaceSymbol(cmdmods: string, popupID: number, result: number): void
+ # clear the message displayed at the command-line
+ :echo ''
+
+ if result <= 0
+ # popup is canceled
+ return
+ endif
+
+ var symTbl: list<dict<any>> = popupID->getwinvar('LspSymbolTable', [])
+ if symTbl->empty()
+ return
+ endif
+ try
+ # Save the current location in the tag stack
+ util.PushCursorToTagStack()
+
+ # if the selected file is already present in a window, then jump to it
+ var fname: string = symTbl[result - 1].file
+ var bnr = fname->bufnr()
+ if cmdmods->empty()
+ var winList: list<number> = bnr->win_findbuf()
+ if winList->empty()
+ # Not present in any window
+ if &modified || &buftype != ''
+ # the current buffer is modified or is not a normal buffer, then
+ # open the file in a new window
+ exe $'split {symTbl[result - 1].file}'
+ else
+ exe $'confirm edit {symTbl[result - 1].file}'
+ endif
+ else
+ # If the target buffer is opened in the current window, then don't
+ # change the window.
+ if bufnr() != bnr
+ # If the target buffer is opened in a window in the current tab
+ # page, then use it.
+ var winID = fname->bufwinid()
+ if winID == -1
+ # not present in the current tab page. Use the first window.
+ winID = winList[0]
+ endif
+ winID->win_gotoid()
+ endif
+ endif
+ else
+ exe $'{cmdmods} split {symTbl[result - 1].file}'
+ endif
+ # Set the previous cursor location mark. Instead of using setpos(), m' is
+ # used so that the current location is added to the jump list.
+ :normal m'
+ setcursorcharpos(symTbl[result - 1].pos.line + 1,
+ util.GetCharIdxWithoutCompChar(bufnr(),
+ symTbl[result - 1].pos) + 1)
+ :normal! zv
+ catch
+ # ignore exceptions
+ endtry
+enddef
+
+# display a list of symbols from the workspace
+def ShowSymbolMenu(lspserver: dict<any>, query: string, cmdmods: string)
+ # Create the popup menu
+ var lnum = &lines - &cmdheight - 2 - 10
+ var popupAttr = {
+ title: 'Workspace Symbol Search',
+ wrap: false,
+ pos: 'topleft',
+ line: lnum,
+ col: 2,
+ minwidth: 60,
+ minheight: 10,
+ maxheight: 10,
+ maxwidth: 60,
+ mapping: false,
+ fixed: 1,
+ close: 'button',
+ filter: function(FilterSymbols, [lspserver]),
+ callback: function('JumpToWorkspaceSymbol', [cmdmods])
+ }
+ lspserver.workspaceSymbolPopup = popup_menu([], popupAttr)
+ lspserver.workspaceSymbolQuery = query
+ prop_type_add('lspworkspacesymbol',
+ {bufnr: lspserver.workspaceSymbolPopup->winbufnr(),
+ highlight: 'Title'})
+ :echo $'Symbol: {query}'
+enddef
+
+# Convert a file name to <filename> (<dirname>) format.
+# Make sure the popup doesn't occupy the entire screen by reducing the width.
+def MakeMenuName(popupWidth: number, fname: string): string
+ var filename: string = fname->fnamemodify(':t')
+ var flen: number = filename->len()
+ var dirname: string = fname->fnamemodify(':h')
+
+ if fname->len() > popupWidth && flen < popupWidth
+ # keep the full file name and reduce directory name length
+ # keep some characters at the beginning and end (equally).
+ # 6 spaces are used for "..." and " ()"
+ var dirsz = (popupWidth - flen - 6) / 2
+ dirname = dirname[: dirsz] .. '...' .. dirname[-dirsz : ]
+ endif
+ var str: string = filename
+ if dirname != '.'
+ str ..= $' ({dirname}/)'
+ endif
+ return str
+enddef
+
+# process the 'workspace/symbol' reply from the LSP server
+# Result: SymbolInformation[] | null
+export def WorkspaceSymbolPopup(lspserver: dict<any>, query: string,
+ symInfo: list<dict<any>>, cmdmods: string)
+ var symbols: list<dict<any>> = []
+ var symbolType: string
+ var fileName: string
+ var symName: string
+
+ # Create a symbol popup menu if it is not present
+ if lspserver.workspaceSymbolPopup->winbufnr() == -1
+ ShowSymbolMenu(lspserver, query, cmdmods)
+ endif
+
+ for symbol in symInfo
+ if !symbol->has_key('location')
+ # ignore entries without location information
+ continue
+ endif
+
+ # interface SymbolInformation
+ fileName = util.LspUriToFile(symbol.location.uri)
+
+ symName = symbol.name
+ if symbol->has_key('containerName') && symbol.containerName != ''
+ symName = $'{symbol.containerName}::{symName}'
+ endif
+ symName ..= $' [{SymbolKindToName(symbol.kind)}]'
+ symName ..= ' ' .. MakeMenuName(
+ lspserver.workspaceSymbolPopup->popup_getpos().core_width,
+ fileName)
+
+ symbols->add({name: symName,
+ file: fileName,
+ pos: symbol.location.range.start})
+ endfor
+ symbols->setwinvar(lspserver.workspaceSymbolPopup, 'LspSymbolTable')
+ lspserver.workspaceSymbolPopup->popup_settext(
+ symbols->copy()->mapnew('v:val.name'))
+enddef
+
+# map the LSP symbol kind number to string
+export def SymbolKindToName(symkind: number): string
+ var symbolMap: list<string> = [
+ '',
+ 'File',
+ 'Module',
+ 'Namespace',
+ 'Package',
+ 'Class',
+ 'Method',
+ 'Property',
+ 'Field',
+ 'Constructor',
+ 'Enum',
+ 'Interface',
+ 'Function',
+ 'Variable',
+ 'Constant',
+ 'String',
+ 'Number',
+ 'Boolean',
+ 'Array',
+ 'Object',
+ 'Key',
+ 'Null',
+ 'EnumMember',
+ 'Struct',
+ 'Event',
+ 'Operator',
+ 'TypeParameter'
+ ]
+ if symkind > 26
+ return ''
+ endif
+ return symbolMap[symkind]
+enddef
+
+def UpdatePeekFilePopup(lspserver: dict<any>, locations: list<dict<any>>)
+ if lspserver.peekSymbolPopup->winbufnr() == -1
+ return
+ endif
+
+ lspserver.peekSymbolFilePopup->popup_close()
+
+ var n = line('.', lspserver.peekSymbolPopup) - 1
+ var [uri, range] = util.LspLocationParse(locations[n])
+ var fname: string = util.LspUriToFile(uri)
+
+ var bnr: number = fname->bufnr()
+ if bnr == -1
+ bnr = fname->bufadd()
+ endif
+
+ var popupAttrs = {
+ title: $"{fname->fnamemodify(':t')} ({fname->fnamemodify(':h')})",
+ wrap: false,
+ fixed: true,
+ minheight: 10,
+ maxheight: 10,
+ minwidth: winwidth(0) - 38,
+ maxwidth: winwidth(0) - 38,
+ cursorline: true,
+ border: [],
+ mapping: false,
+ line: 'cursor+1',
+ col: 1
+ }
+
+ lspserver.peekSymbolFilePopup = popup_create(bnr, popupAttrs)
+ var rstart = range.start
+ var cmds =<< trim eval END
+ :setlocal number
+ [{rstart.line + 1}, 1]->cursor()
+ :normal! z.
+ END
+ win_execute(lspserver.peekSymbolFilePopup, cmds)
+
+ lspserver.peekSymbolFilePopup->clearmatches()
+ var start_col = util.GetLineByteFromPos(bnr, rstart) + 1
+ var end_col = util.GetLineByteFromPos(bnr, range.end)
+ var pos = [[rstart.line + 1,
+ start_col, end_col - start_col + 1]]
+ matchaddpos('Search', pos, 10, -1, {window: lspserver.peekSymbolFilePopup})
+enddef
+
+def LocPopupFilter(lspserver: dict<any>, locations: list<dict<any>>,
+ popup_id: number, key: string): bool
+ popup_filter_menu(popup_id, key)
+ if lspserver.peekSymbolPopup->winbufnr() == -1
+ if lspserver.peekSymbolFilePopup->winbufnr() != -1
+ lspserver.peekSymbolFilePopup->popup_close()
+ endif
+ lspserver.peekSymbolPopup = -1
+ lspserver.peekSymbolFilePopup = -1
+ else
+ UpdatePeekFilePopup(lspserver, locations)
+ endif
+ return true
+enddef
+
+def LocPopupCallback(lspserver: dict<any>, locations: list<dict<any>>,
+ popup_id: number, selIdx: number)
+ if lspserver.peekSymbolFilePopup->winbufnr() != -1
+ lspserver.peekSymbolFilePopup->popup_close()
+ endif
+ lspserver.peekSymbolPopup = -1
+ if selIdx != -1
+ util.PushCursorToTagStack()
+ util.JumpToLspLocation(locations[selIdx - 1], '')
+ endif
+enddef
+
+# Display the locations in a popup menu. Display the corresponding file in
+# an another popup window.
+def PeekLocations(lspserver: dict<any>, locations: list<dict<any>>,
+ title: string)
+ if lspserver.peekSymbolPopup->winbufnr() != -1
+ # If the symbol popup window is already present, close it.
+ lspserver.peekSymbolPopup->popup_close()
+ endif
+
+ var w: number = &columns
+ var fnamelen = float2nr(w * 0.4)
+
+ var curlnum = line('.')
+ var symIdx = 1
+ var curSymIdx = 1
+ var menuItems: list<string> = []
+ for loc in locations
+ var [uri, range] = util.LspLocationParse(loc)
+ var fname: string = util.LspUriToFile(uri)
+ var bnr: number = fname->bufnr()
+ if bnr == -1
+ bnr = fname->bufadd()
+ endif
+ :silent! bnr->bufload()
+
+ var lnum = range.start.line + 1
+ var text: string = bnr->getbufline(lnum)->get(0, '')
+ menuItems->add($'{lnum}: {text}')
+
+ if lnum == curlnum
+ curSymIdx = symIdx
+ endif
+ symIdx += 1
+ endfor
+
+ var popupAttrs = {
+ title: title,
+ wrap: false,
+ pos: 'topleft',
+ line: 'cursor+1',
+ col: winwidth(0) - 34,
+ minheight: 10,
+ maxheight: 10,
+ minwidth: 30,
+ maxwidth: 30,
+ mapping: false,
+ fixed: true,
+ filter: function(LocPopupFilter, [lspserver, locations]),
+ callback: function(LocPopupCallback, [lspserver, locations])
+ }
+ lspserver.peekSymbolPopup = popup_menu(menuItems, popupAttrs)
+ # Select the current symbol in the menu
+ var cmds =<< trim eval END
+ [{curSymIdx}, 1]->cursor()
+ END
+ win_execute(lspserver.peekSymbolPopup, cmds, 'silent!')
+ UpdatePeekFilePopup(lspserver, locations)
+enddef
+
+export def ShowLocations(lspserver: dict<any>, locations: list<dict<any>>,
+ peekSymbol: bool, title: string)
+ if peekSymbol
+ PeekLocations(lspserver, locations, title)
+ return
+ endif
+
+ # create a loclist the location of the locations
+ var qflist: list<dict<any>> = []
+ for loc in locations
+ var [uri, range] = util.LspLocationParse(loc)
+ var fname: string = util.LspUriToFile(uri)
+ var bnr: number = fname->bufnr()
+ if bnr == -1
+ bnr = fname->bufadd()
+ endif
+ :silent! bnr->bufload()
+ var rstart = range.start
+ var text: string = bnr->getbufline(rstart.line + 1)->get(0, '')->trim("\t ", 1)
+ qflist->add({filename: fname,
+ lnum: rstart.line + 1,
+ col: util.GetLineByteFromPos(bnr, rstart) + 1,
+ text: text})
+ endfor
+
+ var save_winid = win_getid()
+
+ if opt.lspOptions.useQuickfixForLocations
+ setqflist([], ' ', {title: title, items: qflist})
+ var mods: string = ''
+ exe $'{mods} copen'
+ else
+ setloclist(0, [], ' ', {title: title, items: qflist})
+ var mods: string = ''
+ exe $'{mods} lopen'
+ endif
+
+ if !opt.lspOptions.keepFocusInReferences
+ save_winid->win_gotoid()
+ endif
+enddef
+
+# Key filter callback function used for the symbol popup window.
+# Vim doesn't close the popup window when the escape key is pressed.
+# This is function supports that.
+def SymbolFilterCB(lspserver: dict<any>, id: number, key: string): bool
+ if key == "\<Esc>"
+ lspserver.peekSymbolPopup->popup_close()
+ return true
+ endif
+
+ return false
+enddef
+
+# Display the file specified by LSP "LocationLink" in a popup window and
+# highlight the range in "location".
+def PeekSymbolLocation(lspserver: dict<any>, location: dict<any>)
+ var [uri, range] = util.LspLocationParse(location)
+ var fname = util.LspUriToFile(uri)
+ var bnum = fname->bufadd()
+ if bnum == 0
+ # Failed to create or find a buffer
+ return
+ endif
+ :silent! bnum->bufload()
+
+ if lspserver.peekSymbolPopup->winbufnr() != -1
+ # If the symbol popup window is already present, close it.
+ lspserver.peekSymbolPopup->popup_close()
+ endif
+ var CbFunc = function(SymbolFilterCB, [lspserver])
+ var popupAttrs = {
+ title: $"{fnamemodify(fname, ':t')} ({fnamemodify(fname, ':h')})",
+ wrap: false,
+ moved: 'any',
+ minheight: 10,
+ maxheight: 10,
+ minwidth: 10,
+ maxwidth: 60,
+ cursorline: true,
+ border: [],
+ mapping: false,
+ filter: CbFunc
+ }
+ lspserver.peekSymbolPopup = popup_atcursor(bnum, popupAttrs)
+
+ # Highlight the symbol name and center the line in the popup
+ var pwid = lspserver.peekSymbolPopup
+ var pwbuf = pwid->winbufnr()
+ var pos: list<number> = []
+ var start_col: number
+ var end_col: number
+ var rstart = range.start
+ start_col = util.GetLineByteFromPos(pwbuf, rstart) + 1
+ end_col = util.GetLineByteFromPos(pwbuf, range.end) + 1
+ pos->add(rstart.line + 1)
+ pos->extend([start_col, end_col - start_col])
+ matchaddpos('Search', [pos], 10, 101, {window: pwid})
+ var cmds =<< trim eval END
+ :setlocal number
+ [{rstart.line + 1}, 1]->cursor()
+ :normal! z.
+ END
+ win_execute(pwid, cmds, 'silent!')
+enddef
+
+# Jump to the definition, declaration or implementation of a symbol.
+# Also, used to peek at the definition, declaration or implementation of a
+# symbol.
+export def GotoSymbol(lspserver: dict<any>, location: dict<any>,
+ peekSymbol: bool, cmdmods: string)
+ if peekSymbol
+ PeekSymbolLocation(lspserver, location)
+ else
+ # Save the current cursor location in the tag stack.
+ util.PushCursorToTagStack()
+ util.JumpToLspLocation(location, cmdmods)
+ endif
+enddef
+
+# Process the LSP server reply message for a 'textDocument/definition' request
+# and return a list of Dicts in a format accepted by the 'tagfunc' option.
+export def TagFunc(lspserver: dict<any>,
+ taglocations: list<dict<any>>,
+ pat: string): list<dict<any>>
+ var retval: list<dict<any>>
+
+ for tagloc in taglocations
+ var tagitem = {}
+ tagitem.name = pat
+
+ var [uri, range] = util.LspLocationParse(tagloc)
+ tagitem.filename = util.LspUriToFile(uri)
+ var bnr = util.LspUriToBufnr(uri)
+ var rstart = range.start
+ var startByteIdx = util.GetLineByteFromPos(bnr, rstart)
+ tagitem.cmd = $"/\\%{rstart.line + 1}l\\%{startByteIdx + 1}c"
+
+ retval->add(tagitem)
+ endfor
+
+ return retval
+enddef
+
+# process SymbolInformation[]
+def ProcessSymbolInfoTable(lspserver: dict<any>,
+ bnr: number,
+ symbolInfoTable: list<dict<any>>,
+ symbolTypeTable: dict<list<dict<any>>>,
+ symbolLineTable: list<dict<any>>)
+ var fname: string
+ var symbolType: string
+ var name: string
+ var r: dict<dict<number>>
+ var symInfo: dict<any>
+
+ for syminfo in symbolInfoTable
+ fname = util.LspUriToFile(syminfo.location.uri)
+ symbolType = SymbolKindToName(syminfo.kind)
+ name = syminfo.name
+ if syminfo->has_key('containerName')
+ if syminfo.containerName != ''
+ name ..= $' [{syminfo.containerName}]'
+ endif
+ endif
+ r = syminfo.location.range
+ lspserver.decodeRange(bnr, r)
+
+ if !symbolTypeTable->has_key(symbolType)
+ symbolTypeTable[symbolType] = []
+ endif
+ symInfo = {name: name, range: r}
+ symbolTypeTable[symbolType]->add(symInfo)
+ symbolLineTable->add(symInfo)
+ endfor
+enddef
+
+# process DocumentSymbol[]
+def ProcessDocSymbolTable(lspserver: dict<any>,
+ bnr: number,
+ docSymbolTable: list<dict<any>>,
+ symbolTypeTable: dict<list<dict<any>>>,
+ symbolLineTable: list<dict<any>>)
+ var symbolType: string
+ var name: string
+ var r: dict<dict<number>>
+ var symInfo: dict<any>
+ var symbolDetail: string
+ var childSymbols: dict<list<dict<any>>>
+
+ for syminfo in docSymbolTable
+ name = syminfo.name
+ symbolType = SymbolKindToName(syminfo.kind)
+ r = syminfo.selectionRange
+ lspserver.decodeRange(bnr, r)
+ if syminfo->has_key('detail')
+ symbolDetail = syminfo.detail
+ endif
+ if !symbolTypeTable->has_key(symbolType)
+ symbolTypeTable[symbolType] = []
+ endif
+ childSymbols = {}
+ if syminfo->has_key('children')
+ ProcessDocSymbolTable(lspserver, bnr, syminfo.children, childSymbols,
+ symbolLineTable)
+ endif
+ symInfo = {name: name, range: r, detail: symbolDetail,
+ children: childSymbols}
+ symbolTypeTable[symbolType]->add(symInfo)
+ symbolLineTable->add(symInfo)
+ endfor
+enddef
+
+# process the 'textDocument/documentSymbol' reply from the LSP server
+# Open a symbols window and display the symbols as a tree
+# Result: DocumentSymbol[] | SymbolInformation[] | null
+export def DocSymbolOutline(lspserver: dict<any>, docSymbol: any, fname: string)
+ var bnr = fname->bufnr()
+ var symbolTypeTable: dict<list<dict<any>>> = {}
+ var symbolLineTable: list<dict<any>> = []
+
+ if docSymbol->empty()
+ # No symbols defined for this file. Clear the outline window.
+ outline.UpdateOutlineWindow(fname, symbolTypeTable, symbolLineTable)
+ return
+ endif
+
+ if docSymbol[0]->has_key('location')
+ # SymbolInformation[]
+ ProcessSymbolInfoTable(lspserver, bnr, docSymbol, symbolTypeTable,
+ symbolLineTable)
+ else
+ # DocumentSymbol[]
+ ProcessDocSymbolTable(lspserver, bnr, docSymbol, symbolTypeTable,
+ symbolLineTable)
+ endif
+
+ # sort the symbols by line number
+ symbolLineTable->sort((a, b) => a.range.start.line - b.range.start.line)
+ outline.UpdateOutlineWindow(fname, symbolTypeTable, symbolLineTable)
+enddef
+
+# Process the list of symbols (LSP interface "SymbolInformation") in
+# "symbolInfoTable". For each symbol, create the name to display in the popup
+# menu along with the symbol range and return the List.
+def GetSymbolsInfoTable(lspserver: dict<any>,
+ bnr: number,
+ symbolInfoTable: list<dict<any>>): list<dict<any>>
+ var symbolTable: list<dict<any>> = []
+ var symbolType: string
+ var name: string
+ var containerName: string
+ var r: dict<dict<number>>
+
+ for syminfo in symbolInfoTable
+ symbolType = SymbolKindToName(syminfo.kind)
+ name = $'{symbolType} : {syminfo.name}'
+ if syminfo->has_key('containerName') && !syminfo.containerName->empty()
+ name ..= $' [{syminfo.containerName}]'
+ endif
+ r = syminfo.location.range
+ lspserver.decodeRange(bnr, r)
+
+ symbolTable->add({name: name, range: r, selectionRange: {}})
+ endfor
+
+ return symbolTable
+enddef
+
+# Process the list of symbols (LSP interface "DocumentSymbol") in
+# "docSymbolTable". For each symbol, create the name to display in the popup
+# menu along with the symbol range and return the List in "symbolTable"
+def GetSymbolsDocSymbol(lspserver: dict<any>,
+ bnr: number,
+ docSymbolTable: list<dict<any>>,
+ symbolTable: list<dict<any>>,
+ parentName: string = '')
+ var symbolType: string
+ var name: string
+ var r: dict<dict<number>>
+ var sr: dict<dict<number>>
+ var symInfo: dict<any>
+
+ for syminfo in docSymbolTable
+ var symName = syminfo.name
+ symbolType = SymbolKindToName(syminfo.kind)->tolower()
+ sr = syminfo.selectionRange
+ lspserver.decodeRange(bnr, sr)
+ r = syminfo.range
+ lspserver.decodeRange(bnr, r)
+ name = $'{symbolType} : {symName}'
+ if parentName != ''
+ name ..= $' [{parentName}]'
+ endif
+ # TODO: Should include syminfo.detail? Will it clutter the menu?
+ symInfo = {name: name, range: r, selectionRange: sr}
+ symbolTable->add(symInfo)
+
+ if syminfo->has_key('children')
+ # Process all the child symbols
+ GetSymbolsDocSymbol(lspserver, bnr, syminfo.children, symbolTable,
+ symName)
+ endif
+ endfor
+enddef
+
+# Highlight the name and the range of lines for the symbol at symTbl[symIdx]
+def SymbolHighlight(symTbl: list<dict<any>>, symIdx: number)
+ prop_remove({type: 'LspSymbolNameProp', all: true})
+ prop_remove({type: 'LspSymbolRangeProp', all: true})
+ if symTbl->empty()
+ return
+ endif
+
+ var r = symTbl[symIdx].range
+ if r->empty()
+ return
+ endif
+ var rangeStart = r.start
+ var rangeEnd = r.end
+ var start_lnum = rangeStart.line + 1
+ var start_col = rangeStart.character + 1
+ var end_lnum = rangeEnd.line + 1
+ var end_col: number
+ var last_lnum = line('$')
+ if end_lnum > line('$')
+ end_lnum = last_lnum
+ end_col = col([last_lnum, '$'])
+ else
+ end_col = rangeEnd.character + 1
+ endif
+ prop_add(start_lnum, start_col,
+ {type: 'LspSymbolRangeProp',
+ end_lnum: end_lnum,
+ end_col: end_col})
+ cursor(start_lnum, 1)
+ :normal! z.
+
+ var sr = symTbl[symIdx].selectionRange
+ if sr->empty()
+ return
+ endif
+ rangeStart = sr.start
+ rangeEnd = sr.end
+ prop_add(rangeStart.line + 1, 1,
+ {type: 'LspSymbolNameProp',
+ start_col: rangeStart.character + 1,
+ end_lnum: rangeEnd.line + 1,
+ end_col: rangeEnd.character + 1})
+enddef
+
+# Callback invoked when an item is selected in the symbol popup menu
+# "symTbl" - list of symbols
+# "symInputPopup" - Symbol search input popup window ID
+# "save_curpos" - Cursor position before invoking the symbol search. If the
+# symbol search is canceled, restore the cursor to this
+# position.
+def SymbolMenuItemSelected(symPopupMenu: number,
+ result: number)
+ var symTblFiltered = symPopupMenu->getwinvar('symbolTableFiltered', [])
+ var symInputPopup = symPopupMenu->getwinvar('symbolInputPopup', 0)
+ var save_curpos = symPopupMenu->getwinvar('saveCurPos', [])
+
+ # Restore the cursor to the location where the command was invoked
+ setpos('.', save_curpos)
+
+ if result > 0
+ # A symbol is selected in the popup menu
+
+ # Set the previous cursor location mark. Instead of using setpos(), m' is
+ # used so that the current location is added to the jump list.
+ :normal m'
+
+ # Jump to the selected symbol location
+ var r = symTblFiltered[result - 1].selectionRange
+ if r->empty()
+ # SymbolInformation doesn't have the selectionRange field
+ r = symTblFiltered[result - 1].range
+ endif
+ setcursorcharpos(r.start.line + 1,
+ util.GetCharIdxWithoutCompChar(bufnr(), r.start) + 1)
+ :normal! zv
+ endif
+ symInputPopup->popup_close()
+ prop_remove({type: 'LspSymbolNameProp', all: true})
+ prop_remove({type: 'LspSymbolRangeProp', all: true})
+enddef
+
+# Key filter function for the symbol popup menu.
+def SymbolMenuFilterKey(symPopupMenu: number,
+ key: string): bool
+ var keyHandled = true
+ var updateInputPopup = false
+ var inputText = symPopupMenu->getwinvar('inputText', '')
+ var symInputPopup = symPopupMenu->getwinvar('symbolInputPopup', 0)
+
+ if key == "\<BS>" || key == "\<C-H>"
+ # Erase a character in the input popup
+ if inputText->len() >= 1
+ inputText = inputText[: -2]
+ updateInputPopup = true
+ else
+ keyHandled = false
+ endif
+ elseif key == "\<C-U>"
+ # Erase all the characters in the input popup
+ inputText = ''
+ updateInputPopup = true
+ elseif key == "\<tab>"
+ || key == "\<C-n>"
+ || key == "\<Down>"
+ || key == "\<ScrollWheelDown>"
+ var ln = getcurpos(symPopupMenu)[1]
+ win_execute(symPopupMenu, "normal! j")
+ if ln == getcurpos(symPopupMenu)[1]
+ win_execute(symPopupMenu, "normal! gg")
+ endif
+ elseif key == "\<S-tab>"
+ || key == "\<C-p>"
+ || key == "\<Up>"
+ || key == "\<ScrollWheelUp>"
+ var ln = getcurpos(symPopupMenu)[1]
+ win_execute(symPopupMenu, "normal! k")
+ if ln == getcurpos(symPopupMenu)[1]
+ win_execute(symPopupMenu, "normal! G")
+ endif
+ elseif key == "\<PageDown>"
+ win_execute(symPopupMenu, "normal! \<C-d>")
+ elseif key == "\<PageUp>"
+ win_execute(symPopupMenu, "normal! \<C-u>")
+ elseif key == "\<C-F>"
+ || key == "\<C-B>"
+ || key == "\<C-Home>"
+ || key == "\<C-End>"
+ win_execute(symPopupMenu, $"normal! {key}")
+ elseif key =~ '^\k$'
+ # A keyword character is typed. Add to the input text and update the
+ # popup
+ inputText ..= key
+ updateInputPopup = true
+ else
+ keyHandled = false
+ endif
+
+ var symTblFiltered: list<dict<any>> = []
+ symTblFiltered = symPopupMenu->getwinvar('symbolTableFiltered', [])
+
+ if updateInputPopup
+ # Update the input popup with the new text and update the symbol popup
+ # window with the matching symbol names.
+ symInputPopup->popup_settext(inputText)
+
+ var symbolTable = symPopupMenu->getwinvar('symbolTable')
+ symTblFiltered = symbolTable->deepcopy()
+ var symbolMatchPos: list<list<number>> = []
+
+ # Get the list of symbols fuzzy matching the entered text
+ if inputText != ''
+ var t = symTblFiltered->matchfuzzypos(inputText, {key: 'name'})
+ symTblFiltered = t[0]
+ symbolMatchPos = t[1]
+ endif
+
+ var popupText: list<dict<any>>
+ var text: list<dict<any>>
+ if !symbolMatchPos->empty()
+ # Generate a list of symbol names and the corresponding text properties
+ # to highlight the matching characters.
+ popupText = symTblFiltered->mapnew((idx, val): dict<any> => ({
+ text: val.name,
+ props: symbolMatchPos[idx]->mapnew((_, w: number): dict<any> => ({
+ col: w + 1,
+ length: 1,
+ type: 'LspSymbolMatch'}
+ ))}
+ ))
+ else
+ popupText = symTblFiltered->mapnew((idx, val): dict<string> => {
+ return {text: val.name}
+ })
+ endif
+ symPopupMenu->popup_settext(popupText)
+
+ # Select the first symbol and highlight the corresponding text range
+ win_execute(symPopupMenu, 'cursor(1, 1)')
+ SymbolHighlight(symTblFiltered, 0)
+ endif
+
+ # Save the filtered symbol table and the search text in popup window
+ # variables
+ setwinvar(symPopupMenu, 'inputText', inputText)
+ setwinvar(symPopupMenu, 'symbolTableFiltered', symTblFiltered)
+
+ if !keyHandled
+ # Use the default handler for the key
+ symPopupMenu->popup_filter_menu(key)
+ endif
+
+ # Highlight the name and range of the selected symbol
+ var lnum = line('.', symPopupMenu) - 1
+ if lnum >= 0
+ SymbolHighlight(symTblFiltered, lnum)
+ endif
+
+ return true
+enddef
+
+# Display the symbols popup menu
+def SymbolPopupMenu(symbolTable: list<dict<any>>)
+ var curLine = line('.')
+ var curSymIdx = 0
+
+ # Get the names of all the symbols. Also get the index of the symbol under
+ # the cursor.
+ var symNames = symbolTable->mapnew((idx, val): string => {
+ var r = val.range
+ if !r->empty() && curSymIdx == 0
+ if curLine >= r.start.line + 1 && curLine <= r.end.line + 1
+ curSymIdx = idx
+ endif
+ endif
+ return val.name
+ })
+
+ var symInputPopupAttr = {
+ title: 'Select Symbol',
+ wrap: false,
+ pos: 'topleft',
+ line: &lines - 14,
+ col: 10,
+ minwidth: 60,
+ minheight: 1,
+ maxheight: 1,
+ maxwidth: 60,
+ fixed: 1,
+ close: 'button',
+ border: []
+ }
+ var symInputPopup = popup_create('', symInputPopupAttr)
+
+ var symNamesPopupattr = {
+ wrap: false,
+ pos: 'topleft',
+ line: &lines - 11,
+ col: 10,
+ minwidth: 60,
+ minheight: 10,
+ maxheight: 10,
+ maxwidth: 60,
+ fixed: 1,
+ border: [0, 0, 0, 0],
+ callback: SymbolMenuItemSelected,
+ filter: SymbolMenuFilterKey,
+ }
+ var symPopupMenu = popup_menu(symNames, symNamesPopupattr)
+
+ # Save the state in the popup menu window variables
+ setwinvar(symPopupMenu, 'symbolTable', symbolTable)
+ setwinvar(symPopupMenu, 'symbolTableFiltered', symbolTable->deepcopy())
+ setwinvar(symPopupMenu, 'symbolInputPopup', symInputPopup)
+ setwinvar(symPopupMenu, 'saveCurPos', getcurpos())
+ prop_type_add('LspSymbolMatch', {bufnr: symPopupMenu->winbufnr(),
+ highlight: 'Title',
+ override: true})
+
+ # Start with the symbol under the cursor
+ var cmds =<< trim eval END
+ [{curSymIdx + 1}, 1]->cursor()
+ :normal! z.
+ END
+ win_execute(symPopupMenu, cmds, 'silent!')
+
+ # Highlight the name and range of the first symbol
+ SymbolHighlight(symbolTable, curSymIdx)
+enddef
+
+# process the 'textDocument/documentSymbol' reply from the LSP server
+# Result: DocumentSymbol[] | SymbolInformation[] | null
+# Display the symbols in a popup window and jump to the selected symbol
+export def DocSymbolPopup(lspserver: dict<any>, docSymbol: any, fname: string)
+ var symList: list<dict<any>> = []
+
+ if docSymbol->empty()
+ return
+ endif
+
+ var bnr = fname->bufnr()
+
+ if docSymbol[0]->has_key('location')
+ # SymbolInformation[]
+ symList = GetSymbolsInfoTable(lspserver, bnr, docSymbol)
+ else
+ # DocumentSymbol[]
+ GetSymbolsDocSymbol(lspserver, bnr, docSymbol, symList)
+ endif
+
+ :redraw!
+ SymbolPopupMenu(symList)
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/textedit.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/textedit.vim
new file mode 100644
index 0000000..ee4cc4a
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/textedit.vim
@@ -0,0 +1,321 @@
+vim9script
+
+import './util.vim'
+
+# sort the list of edit operations in the descending order of line and column
+# numbers.
+# 'a': {'A': [lnum, col], 'B': [lnum, col]}
+# 'b': {'A': [lnum, col], 'B': [lnum, col]}
+def Edit_sort_func(a: dict<any>, b: dict<any>): number
+ # line number
+ if a.A[0] != b.A[0]
+ return b.A[0] - a.A[0]
+ endif
+ # column number
+ if a.A[1] != b.A[1]
+ return b.A[1] - a.A[1]
+ endif
+
+ # Assume that the LSP sorted the lines correctly to begin with
+ return b.idx - a.idx
+enddef
+
+# Replaces text in a range with new text.
+#
+# CAUTION: Changes in-place!
+#
+# 'lines': Original list of strings
+# 'A': Start position; [line, col]
+# 'B': End position [line, col]
+# 'new_lines' A list of strings to replace the original
+#
+# returns the modified 'lines'
+def Set_lines(lines: list<string>, A: list<number>, B: list<number>,
+ new_lines: list<string>): list<string>
+ var i_0: number = A[0]
+
+ # If it extends past the end, truncate it to the end. This is because the
+ # way the LSP describes the range including the last newline is by
+ # specifying a line number after what we would call the last line.
+ var numlines: number = lines->len()
+ var i_n = [B[0], numlines - 1]->min()
+
+ if i_0 < 0 || i_0 >= numlines || i_n < 0 || i_n >= numlines
+ #util.WarnMsg("set_lines: Invalid range, A = " .. A->string()
+ # .. ", B = " .. B->string() .. ", numlines = " .. numlines
+ # .. ", new lines = " .. new_lines->string())
+ var msg = $"set_lines: Invalid range, A = {A->string()}"
+ msg ..= $", B = {B->string()}, numlines = {numlines}"
+ msg ..= $", new lines = {new_lines->string()}"
+ util.WarnMsg(msg)
+ return lines
+ endif
+
+ # save the prefix and suffix text before doing the replacements
+ var prefix: string = ''
+ var suffix: string = lines[i_n][B[1] :]
+ if A[1] > 0
+ prefix = lines[i_0][0 : A[1] - 1]
+ endif
+
+ var new_lines_len: number = new_lines->len()
+
+ #echomsg $"i_0 = {i_0}, i_n = {i_n}, new_lines = {string(new_lines)}"
+ var n: number = i_n - i_0 + 1
+ if n != new_lines_len
+ if n > new_lines_len
+ # remove the deleted lines
+ lines->remove(i_0, i_0 + n - new_lines_len - 1)
+ else
+ # add empty lines for newly the added lines (will be replaced with the
+ # actual lines below)
+ lines->extend(repeat([''], new_lines_len - n), i_0)
+ endif
+ endif
+ #echomsg $"lines(1) = {string(lines)}"
+
+ # replace the previous lines with the new lines
+ for i in new_lines_len->range()
+ lines[i_0 + i] = new_lines[i]
+ endfor
+ #echomsg $"lines(2) = {string(lines)}"
+
+ # append the suffix (if any) to the last line
+ if suffix != ''
+ var i = i_0 + new_lines_len - 1
+ lines[i] = lines[i] .. suffix
+ endif
+ #echomsg $"lines(3) = {string(lines)}"
+
+ # prepend the prefix (if any) to the first line
+ if prefix != ''
+ lines[i_0] = prefix .. lines[i_0]
+ endif
+ #echomsg $"lines(4) = {string(lines)}"
+
+ return lines
+enddef
+
+# Apply set of text edits to the specified buffer
+# The text edit logic is ported from the Neovim lua implementation
+export def ApplyTextEdits(bnr: number, text_edits: list<dict<any>>): void
+ if text_edits->empty()
+ return
+ endif
+
+ # if the buffer is not loaded, load it and make it a listed buffer
+ :silent! bnr->bufload()
+ setbufvar(bnr, '&buflisted', true)
+
+ var start_line: number = 4294967295 # 2 ^ 32
+ var finish_line: number = -1
+ var updated_edits: list<dict<any>> = []
+ var start_row: number
+ var start_col: number
+ var end_row: number
+ var end_col: number
+
+ # create a list of buffer positions where the edits have to be applied.
+ var idx = 0
+ for e in text_edits
+ # Adjust the start and end columns for multibyte characters
+ var r = e.range
+ var rstart: dict<any> = r.start
+ var rend: dict<any> = r.end
+ start_row = rstart.line
+ start_col = util.GetCharIdxWithoutCompChar(bnr, rstart)
+ end_row = rend.line
+ end_col = util.GetCharIdxWithoutCompChar(bnr, rend)
+ start_line = [rstart.line, start_line]->min()
+ finish_line = [rend.line, finish_line]->max()
+
+ updated_edits->add({A: [start_row, start_col],
+ B: [end_row, end_col],
+ idx: idx,
+ lines: e.newText->split("\n", true)})
+ idx += 1
+ endfor
+
+ # Reverse sort the edit operations by descending line and column numbers so
+ # that they can be applied without interfering with each other.
+ updated_edits->sort('Edit_sort_func')
+
+ var lines: list<string> = bnr->getbufline(start_line + 1, finish_line + 1)
+ var fix_eol: bool = bnr->getbufvar('&fixeol')
+ var set_eol = fix_eol && bnr->getbufinfo()[0].linecount <= finish_line + 1
+ if !lines->empty() && set_eol && lines[-1]->len() != 0
+ lines->add('')
+ endif
+
+ #echomsg $'lines(1) = {string(lines)}'
+ #echomsg updated_edits
+
+ for e in updated_edits
+ var A: list<number> = [e.A[0] - start_line, e.A[1]]
+ var B: list<number> = [e.B[0] - start_line, e.B[1]]
+ lines = Set_lines(lines, A, B, e.lines)
+ endfor
+
+ #echomsg $'lines(2) = {string(lines)}'
+
+ # If the last line is empty and we need to set EOL, then remove it.
+ if !lines->empty() && set_eol && lines[-1]->len() == 0
+ lines->remove(-1)
+ endif
+
+ #echomsg $'ApplyTextEdits: start_line = {start_line}, finish_line = {finish_line}'
+ #echomsg $'lines = {string(lines)}'
+
+ # if the buffer is empty, appending lines before the first line adds an
+ # extra empty line at the end. Delete the empty line after appending the
+ # lines.
+ var dellastline: bool = false
+ if start_line == 0 && bnr->getbufinfo()[0].linecount == 1 &&
+ bnr->getbufline(1)->get(0, '')->empty()
+ dellastline = true
+ endif
+
+ # Now we apply the textedits to the actual buffer.
+ # In theory we could just delete all old lines and append the new lines.
+ # This would however cause the cursor to change position: It will always be
+ # on the last line added.
+ #
+ # Luckily there is an even simpler solution, that has no cursor sideeffects.
+ #
+ # Logically this method is split into the following three cases:
+ #
+ # 1. The number of new lines is equal to the number of old lines:
+ # Just replace the lines inline with setbufline()
+ #
+ # 2. The number of new lines is greater than the old ones:
+ # First append the missing lines at the **end** of the range, then use
+ # setbufline() again. This does not cause the cursor to change position.
+ #
+ # 3. The number of new lines is less than before:
+ # First use setbufline() to replace the lines that we can replace.
+ # Then remove superfluous lines.
+ #
+ # Luckily, the three different cases exist only logically, we can reduce
+ # them to a single case practically, because appendbufline() does not append
+ # anything if an empty list is passed just like deletebufline() does not
+ # delete anything, if the last line of the range is before the first line.
+ # We just need to be careful with all indices.
+ appendbufline(bnr, finish_line + 1, lines[finish_line - start_line + 1 : -1])
+ setbufline(bnr, start_line + 1, lines)
+ deletebufline(bnr, start_line + 1 + lines->len(), finish_line + 1)
+
+ if dellastline
+ bnr->deletebufline(bnr->getbufinfo()[0].linecount)
+ endif
+enddef
+
+# interface TextDocumentEdit
+def ApplyTextDocumentEdit(textDocEdit: dict<any>)
+ var bnr: number = util.LspUriToBufnr(textDocEdit.textDocument.uri)
+ if bnr == -1
+ util.ErrMsg($'Text Document edit, buffer {textDocEdit.textDocument.uri} is not found')
+ return
+ endif
+ ApplyTextEdits(bnr, textDocEdit.edits)
+enddef
+
+# interface CreateFile
+# Create the "createFile.uri" file
+def FileCreate(createFile: dict<any>)
+ var fname: string = util.LspUriToFile(createFile.uri)
+ var opts: dict<bool> = createFile->get('options', {})
+ var ignoreIfExists: bool = opts->get('ignoreIfExists', true)
+ var overwrite: bool = opts->get('overwrite', false)
+
+ # LSP Spec: Overwrite wins over `ignoreIfExists`
+ if fname->filereadable() && ignoreIfExists && !overwrite
+ return
+ endif
+
+ fname->fnamemodify(':p:h')->mkdir('p')
+ []->writefile(fname)
+ fname->bufadd()
+enddef
+
+# interface DeleteFile
+# Delete the "deleteFile.uri" file
+def FileDelete(deleteFile: dict<any>)
+ var fname: string = util.LspUriToFile(deleteFile.uri)
+ var opts: dict<bool> = deleteFile->get('options', {})
+ var recursive: bool = opts->get('recursive', false)
+ var ignoreIfNotExists: bool = opts->get('ignoreIfNotExists', true)
+
+ if !fname->filereadable() && ignoreIfNotExists
+ return
+ endif
+
+ var flags: string = ''
+ if recursive
+ # # NOTE: is this a dangerous operation? The LSP server can send a
+ # # DeleteFile message to recursively delete all the files in the disk.
+ # flags = 'rf'
+ util.ErrMsg($'Recursively deleting files is not supported')
+ return
+ elseif fname->isdirectory()
+ flags = 'd'
+ endif
+ var bnr: number = fname->bufadd()
+ fname->delete(flags)
+ exe $'{bnr}bwipe!'
+enddef
+
+# interface RenameFile
+# Rename file "renameFile.oldUri" to "renameFile.newUri"
+def FileRename(renameFile: dict<any>)
+ var old_fname: string = util.LspUriToFile(renameFile.oldUri)
+ var new_fname: string = util.LspUriToFile(renameFile.newUri)
+
+ var opts: dict<bool> = renameFile->get('options', {})
+ var overwrite: bool = opts->get('overwrite', false)
+ var ignoreIfExists: bool = opts->get('ignoreIfExists', true)
+
+ if new_fname->filereadable() && (!overwrite || ignoreIfExists)
+ return
+ endif
+
+ old_fname->rename(new_fname)
+enddef
+
+# interface WorkspaceEdit
+export def ApplyWorkspaceEdit(workspaceEdit: dict<any>)
+ if workspaceEdit->has_key('documentChanges')
+ for change in workspaceEdit.documentChanges
+ if change->has_key('kind')
+ if change.kind == 'create'
+ FileCreate(change)
+ elseif change.kind == 'delete'
+ FileDelete(change)
+ elseif change.kind == 'rename'
+ FileRename(change)
+ else
+ util.ErrMsg($'Unsupported change in workspace edit [{change.kind}]')
+ endif
+ else
+ ApplyTextDocumentEdit(change)
+ endif
+ endfor
+ return
+ endif
+
+ if !workspaceEdit->has_key('changes')
+ return
+ endif
+
+ for [uri, changes] in workspaceEdit.changes->items()
+ var bnr: number = util.LspUriToBufnr(uri)
+ if bnr == 0
+ # file is not present
+ continue
+ endif
+
+ # interface TextEdit
+ ApplyTextEdits(bnr, changes)
+ endfor
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/typehierarchy.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/typehierarchy.vim
new file mode 100644
index 0000000..8786b1b
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/typehierarchy.vim
@@ -0,0 +1,174 @@
+vim9script
+
+# Functions for dealing with type hierarchy (super types/sub types)
+
+import './util.vim'
+import './symbol.vim'
+
+# Parse the type hierarchy in "typeHier" and displays a tree of type names
+# in the current buffer. This function is called recursively to display the
+# super/sub type hierarchy.
+#
+# Returns the line number where the next type name should be added.
+def TypeTreeGenerate(isSuper: bool, typeHier: dict<any>, pfx_arg: string,
+ typeTree: list<string>, typeUriMap: list<dict<any>>)
+
+ var itemHasChildren = false
+ if isSuper
+ if typeHier->has_key('parents') && !typeHier.parents->empty()
+ itemHasChildren = true
+ endif
+ else
+ if typeHier->has_key('children') && !typeHier.children->empty()
+ itemHasChildren = true
+ endif
+ endif
+
+ var itemBranchPfx: string
+ if itemHasChildren
+ itemBranchPfx = '▾ '
+ else
+ itemBranchPfx = pfx_arg->empty() ? '' : ' '
+ endif
+
+ var typestr: string
+ var kindstr = symbol.SymbolKindToName(typeHier.kind)
+ if kindstr != ''
+ typestr = $'{pfx_arg}{itemBranchPfx}{typeHier.name} ({kindstr[0]})'
+ else
+ typestr = $'{pfx_arg}{itemBranchPfx}{typeHier.name}'
+ endif
+ typeTree->add(typestr)
+ typeUriMap->add(typeHier)
+
+ # last item to process
+ if !itemHasChildren
+ return
+ endif
+
+ var items: list<dict<any>>
+ items = isSuper ? typeHier.parents : typeHier.children
+
+ for item in items
+ TypeTreeGenerate(isSuper, item, $'{pfx_arg}| ', typeTree, typeUriMap)
+ endfor
+enddef
+
+# Display a popup with the file containing a type and highlight the line and
+# the type name.
+def UpdateTypeHierFileInPopup(lspserver: dict<any>, typeUriMap: list<dict<any>>)
+ if lspserver.typeHierPopup->winbufnr() == -1
+ return
+ endif
+
+ lspserver.typeHierFilePopup->popup_close()
+
+ var n = line('.', lspserver.typeHierPopup) - 1
+ var fname: string = util.LspUriToFile(typeUriMap[n].uri)
+
+ var bnr = fname->bufadd()
+ if bnr == 0
+ return
+ endif
+
+ var popupAttrs = {
+ title: $"{fname->fnamemodify(':t')} ({fname->fnamemodify(':h')})",
+ wrap: false,
+ fixed: true,
+ minheight: 10,
+ maxheight: 10,
+ minwidth: winwidth(0) - 38,
+ maxwidth: winwidth(0) - 38,
+ cursorline: true,
+ border: [],
+ line: 'cursor+1',
+ col: 1
+ }
+ lspserver.typeHierFilePopup = popup_create(bnr, popupAttrs)
+ var cmds =<< trim eval END
+ [{typeUriMap[n].range.start.line + 1}, 1]->cursor()
+ :normal! z.
+ END
+ win_execute(lspserver.typeHierFilePopup, cmds)
+
+ lspserver.typeHierFilePopup->clearmatches()
+ var start_col = util.GetLineByteFromPos(bnr,
+ typeUriMap[n].selectionRange.start) + 1
+ var end_col = util.GetLineByteFromPos(bnr, typeUriMap[n].selectionRange.end)
+ var pos = [[typeUriMap[n].selectionRange.start.line + 1,
+ start_col, end_col - start_col + 1]]
+ matchaddpos('Search', pos, 10, -1, {window: lspserver.typeHierFilePopup})
+enddef
+
+def TypeHierPopupFilter(lspserver: dict<any>, typeUriMap: list<dict<any>>,
+ popupID: number, key: string): bool
+ popupID->popup_filter_menu(key)
+ if lspserver.typeHierPopup->winbufnr() == -1
+ # popup is closed
+ if lspserver.typeHierFilePopup->winbufnr() != -1
+ lspserver.typeHierFilePopup->popup_close()
+ endif
+ lspserver.typeHierFilePopup = -1
+ lspserver.typeHierPopup = -1
+ else
+ UpdateTypeHierFileInPopup(lspserver, typeUriMap)
+ endif
+
+ return true
+enddef
+
+def TypeHierPopupCallback(lspserver: dict<any>, typeUriMap: list<dict<any>>,
+ popupID: number, selIdx: number)
+ if lspserver.typeHierFilePopup->winbufnr() != -1
+ lspserver.typeHierFilePopup->popup_close()
+ endif
+ lspserver.typeHierFilePopup = -1
+ lspserver.typeHierPopup = -1
+
+ if selIdx <= 0
+ # popup is canceled
+ return
+ endif
+
+ # Save the current cursor location in the tag stack.
+ util.PushCursorToTagStack()
+ util.JumpToLspLocation(typeUriMap[selIdx - 1], '')
+enddef
+
+# Show the super or sub type hierarchy items "types" as a tree in a popup
+# window
+export def ShowTypeHierarchy(lspserver: dict<any>, isSuper: bool, types: dict<any>)
+
+ if lspserver.typeHierPopup->winbufnr() != -1
+ # If the type hierarchy popup window is already present, close it.
+ lspserver.typeHierPopup->popup_close()
+ endif
+
+ var typeTree: list<string>
+ var typeUriMap: list<dict<any>>
+
+ # Generate a tree of the type hierarchy items
+ TypeTreeGenerate(isSuper, types, '', typeTree, typeUriMap)
+
+ # Display a popup window with the type hierarchy tree and a popup window for
+ # the file.
+ var popupAttrs = {
+ title: $'{isSuper ? "Super" : "Sub"}Type Hierarchy',
+ wrap: 0,
+ pos: 'topleft',
+ line: 'cursor+1',
+ col: winwidth(0) - 34,
+ minheight: 10,
+ maxheight: 10,
+ minwidth: 30,
+ maxwidth: 30,
+ mapping: false,
+ fixed: true,
+ filter: function(TypeHierPopupFilter, [lspserver, typeUriMap]),
+ callback: function(TypeHierPopupCallback, [lspserver, typeUriMap])
+ }
+ lspserver.typeHierPopup = popup_menu(typeTree, popupAttrs)
+ UpdateTypeHierFileInPopup(lspserver, typeUriMap)
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
diff --git a/vim/pack/downloads/opt/lsp/autoload/lsp/util.vim b/vim/pack/downloads/opt/lsp/autoload/lsp/util.vim
new file mode 100644
index 0000000..eb0a64f
--- /dev/null
+++ b/vim/pack/downloads/opt/lsp/autoload/lsp/util.vim
@@ -0,0 +1,364 @@
+vim9script
+
+# Display an info message
+export def InfoMsg(msg: string)
+ :echohl Question
+ :echomsg $'Info: {msg}'
+ :echohl None
+enddef
+
+# Display a warning message
+export def WarnMsg(msg: string)
+ :echohl WarningMsg
+ :echomsg $'Warn: {msg}'
+ :echohl None
+enddef
+
+# Display an error message
+export def ErrMsg(msg: string)
+ :echohl Error
+ :echomsg $'Error: {msg}'
+ :echohl None
+enddef
+
+# Lsp server trace log directory
+var lsp_log_dir: string
+if has('unix')
+ lsp_log_dir = '/tmp/'
+else
+ lsp_log_dir = $TEMP .. '\\'
+endif
+
+# Log a message from the LSP server. stderr is true for logging messages
+# from the standard error and false for stdout.
+export def TraceLog(fname: string, stderr: bool, msg: string)
+ if stderr
+ writefile(msg->split("\n"), $'{lsp_log_dir}{fname}', 'a')
+ else
+ writefile([msg], $'{lsp_log_dir}{fname}', 'a')
+ endif
+enddef
+
+# Empty out the LSP server trace logs
+export def ClearTraceLogs(fname: string)
+ writefile([], $'{lsp_log_dir}{fname}')
+enddef
+
+# Open the LSP server debug messages file.
+export def ServerMessagesShow(fname: string)
+ var fullname = $'{lsp_log_dir}{fname}'
+ if !filereadable(fullname)
+ WarnMsg($'File {fullname} is not found')
+ return
+ endif
+ var wid = fullname->bufwinid()
+ if wid == -1
+ exe $'split {fullname}'
+ else
+ win_gotoid(wid)
+ endif
+ setlocal autoread
+ setlocal bufhidden=wipe
+ setlocal nomodified
+ setlocal nomodifiable
+enddef
+
+# Parse a LSP Location or LocationLink type and return a List with two items.
+# The first item is the DocumentURI and the second item is the Range.
+export def LspLocationParse(lsploc: dict<any>): list<any>
+ if lsploc->has_key('targetUri')
+ # LocationLink
+ return [lsploc.targetUri, lsploc.targetSelectionRange]
+ else
+ # Location
+ return [lsploc.uri, lsploc.range]
+ endif
+enddef
+
+# Convert a LSP file URI (file://<absolute_path>) to a Vim file name
+export def LspUriToFile(uri: string): string
+ # Replace all the %xx numbers (e.g. %20 for space) in the URI to character
+ var uri_decoded: string = substitute(uri, '%\(\x\x\)',
+ '\=nr2char(str2nr(submatch(1), 16))', 'g')
+
+ # File URIs on MS-Windows start with file:///[a-zA-Z]:'
+ if uri_decoded =~? '^file:///\a:'
+ # MS-Windows URI
+ uri_decoded = uri_decoded[8 : ]
+ uri_decoded = uri_decoded->substitute('/', '\\', 'g')
+ # On GNU/Linux (pattern not end with `:`)
+ elseif uri_decoded =~? '^file:///\a'
+ uri_decoded = uri_decoded[7 : ]
+ endif
+
+ return uri_decoded
+enddef
+
+# Convert a LSP file URI (file://<absolute_path>) to a Vim buffer number.
+# If the file is not in a Vim buffer, then adds the buffer.
+# Returns 0 on error.
+export def LspUriToBufnr(uri: string): number
+ return LspUriToFile(uri)->bufadd()
+enddef
+
+# Returns if the URI refers to a remote file (e.g. ssh://)
+# Credit: vim-lsp plugin
+export def LspUriRemote(uri: string): bool
+ return uri =~ '^\w\+::' || uri =~ '^[a-z][a-z0-9+.-]*://'
+enddef
+
+var resolvedUris = {}
+
+# Convert a Vim filename to an LSP URI (file://<absolute_path>)
+export def LspFileToUri(fname: string): string
+ var fname_full: string = fname->fnamemodify(':p')
+
+ if resolvedUris->has_key(fname_full)
+ return resolvedUris[fname_full]
+ endif
+
+ var uri: string = fname_full
+
+ if has("win32unix")
+ # We're in Cygwin, convert POSIX style paths to Windows style.
+ # The substitution is to remove the '^@' escape character from the end of
+ # line.
+ uri = system($'cygpath -m {uri}')->substitute('^\(\p*\).*$', '\=submatch(1)', "")
+ endif
+
+ var on_windows: bool = false
+ if uri =~? '^\a:'
+ on_windows = true
+ endif
+
+ if on_windows
+ # MS-Windows
+ uri = uri->substitute('\\', '/', 'g')
+ endif
+
+ uri = uri->substitute('\([^A-Za-z0-9-._~:/]\)',
+ '\=printf("%%%02x", char2nr(submatch(1)))', 'g')
+
+ if on_windows
+ uri = $'file:///{uri}'
+ else
+ uri = $'file://{uri}'
+ endif
+
+ resolvedUris[fname_full] = uri
+ return uri
+enddef
+
+# Convert a Vim buffer number to an LSP URI (file://<absolute_path>)
+export def LspBufnrToUri(bnr: number): string
+ return LspFileToUri(bnr->bufname())
+enddef
+
+# Returns the byte number of the specified LSP position in buffer "bnr".
+# LSP's line and characters are 0-indexed.
+# Vim's line and columns are 1-indexed.
+# Returns a zero-indexed column.
+export def GetLineByteFromPos(bnr: number, pos: dict<number>): number
+ var col: number = pos.character
+ # When on the first character, we can ignore the difference between byte and
+ # character
+ if col <= 0
+ return col
+ endif
+
+ # Need a loaded buffer to read the line and compute the offset
+ :silent! bnr->bufload()
+
+ var ltext: string = bnr->getbufline(pos.line + 1)->get(0, '')
+ if ltext->empty()
+ return col
+ endif
+
+ var byteIdx = ltext->byteidxcomp(col)
+ if byteIdx != -1
+ return byteIdx
+ endif
+
+ return col
+enddef
+
+# Get the index of the character at [pos.line, pos.character] in buffer "bnr"
+# without counting the composing characters. The LSP server counts composing
+# characters as separate characters whereas Vim string indexing ignores the
+# composing characters.
+export def GetCharIdxWithoutCompChar(bnr: number, pos: dict<number>): number
+ var col: number = pos.character
+ # When on the first character, nothing to do.
+ if col <= 0
+ return col
+ endif
+
+ # Need a loaded buffer to read the line and compute the offset
+ :silent! bnr->bufload()
+
+ var ltext: string = bnr->getbufline(pos.line + 1)->get(0, '')
+ if ltext->empty()
+ return col
+ endif
+
+ # Convert the character index that includes composing characters as separate
+ # characters to a byte index and then back to a character index ignoring the
+ # composing characters.
+ var byteIdx = ltext->byteidxcomp(col)
+ if byteIdx != -1
+ if byteIdx == ltext->strlen()
+ # Byte index points to the byte after the last byte.
+ return ltext->strcharlen()
+ else
+ return ltext->charidx(byteIdx, false)
+ endif
+ endif
+
+ return col
+enddef
+
+# Get the index of the character at [pos.line, pos.character] in buffer "bnr"
+# counting the composing characters as separate characters. The LSP server
+# counts composing characters as separate characters whereas Vim string
+# indexing ignores the composing characters.
+export def GetCharIdxWithCompChar(ltext: string, charIdx: number): number
+ # When on the first character, nothing to do.
+ if charIdx <= 0 || ltext->empty()
+ return charIdx
+ endif
+
+ # Convert the character index that doesn't include composing characters as
+ # separate characters to a byte index and then back to a character index
+ # that includes the composing characters as separate characters
+ var byteIdx = ltext->byteidx(charIdx)
+ if byteIdx != -1
+ if byteIdx == ltext->strlen()
+ return ltext->strchars()
+ else
+ return ltext->charidx(byteIdx, true)
+ endif
+ endif
+
+ return charIdx
+enddef
+
+# push the current location on to the tag stack
+export def PushCursorToTagStack()
+ settagstack(winnr(), {items: [
+ {
+ bufnr: bufnr(),
+ from: getpos('.'),
+ matchnr: 1,
+ tagname: expand('<cword>')
+ }]}, 't')
+enddef
+
+# Jump to the LSP "location". The "location" contains the file name, line
+# number and character number. The user specified window command modifiers
+# (e.g. topleft) are in "cmdmods".
+export def JumpToLspLocation(location: dict<any>, cmdmods: string)
+ var [uri, range] = LspLocationParse(location)
+ var fname = LspUriToFile(uri)
+
+ # jump to the file and line containing the symbol
+ var bnr: number = fname->bufnr()
+ if cmdmods->empty()
+ if bnr == bufnr()
+ # Set the previous cursor location mark. Instead of using setpos(), m' is
+ # used so that the current location is added to the jump list.
+ :normal m'
+ else
+ var wid = fname->bufwinid()
+ if wid != -1
+ wid->win_gotoid()
+ else
+ if bnr != -1
+ # Reuse an existing buffer. If the current buffer has unsaved changes
+ # and 'hidden' is not set or if the current buffer is a special
+ # buffer, then open the buffer in a new window.
+ if (&modified && !&hidden) || &buftype != ''
+ exe $'belowright sbuffer {bnr}'
+ else
+ exe $'buf {bnr}'
+ endif
+ else
+ if (&modified && !&hidden) || &buftype != ''
+ # if the current buffer has unsaved changes and 'hidden' is not set,
+ # or if the current buffer is a special buffer, then open the file
+ # in a new window
+ exe $'belowright split {fname}'
+ else
+ exe $'edit {fname}'
+ endif
+ endif
+ endif
+ endif
+ else
+ if bnr == -1
+ exe $'{cmdmods} split {fname}'
+ else
+ # Use "sbuffer" so that the 'switchbuf' option settings are used.
+ exe $'{cmdmods} sbuffer {bnr}'
+ endif
+ endif
+ var rstart = range.start
+ setcursorcharpos(rstart.line + 1,
+ GetCharIdxWithoutCompChar(bufnr(), rstart) + 1)
+ :normal! zv
+enddef
+
+# indexof() function is not present in older Vim 9 versions. So use this
+# function.
+export def Indexof(list: list<any>, CallbackFn: func(number, any): bool): number
+ var ix = 0
+ for val in list
+ if CallbackFn(ix, val)
+ return ix
+ endif
+ ix += 1
+ endfor
+ return -1
+enddef
+
+# Find the nearest root directory containing a file or directory name from the
+# list of names in "files" starting with the directory "startDir".
+# Based on a similar implementation in the vim-lsp plugin.
+# Searches upwards starting with the directory "startDir".
+# If a file name ends with '/' or '\', then it is a directory name, otherwise
+# it is a file name.
+# Returns '' if none of the file and directory names in "files" can be found
+# in one of the parent directories.
+export def FindNearestRootDir(startDir: string, files: list<any>): string
+ var foundDirs: dict<bool> = {}
+
+ for file in files
+ if file->type() != v:t_string || file->empty()
+ continue
+ endif
+ var isDir = file[-1 : ] == '/' || file[-1 : ] == '\'
+ var relPath: string
+ if isDir
+ relPath = finddir(file, $'{startDir};')
+ else
+ relPath = findfile(file, $'{startDir};')
+ endif
+ if relPath->empty()
+ continue
+ endif
+ var rootDir = relPath->fnamemodify(isDir ? ':p:h:h' : ':p:h')
+ foundDirs[rootDir] = true
+ endfor
+ if foundDirs->empty()
+ return ''
+ endif
+
+ # Sort the directory names by length
+ var sortedList: list<string> = foundDirs->keys()->sort((a, b) => {
+ return b->len() - a->len()
+ })
+
+ # choose the longest matching path (the nearest directory from "startDir")
+ return sortedList[0]
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2