# 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
+
+ # 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
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
# Functions related to handling LSP code lens
-export def ProcessCodeLens(lspserver: dict<any>, codeLensItems: list<dict<any>>)
+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(item)
+ item = lspserver.resolveCodeLens(bnr, item)
if item->empty()
continue
endif
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
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,
import './handlers.vim'
import './util.vim'
import './capabilities.vim'
+import './offset.vim'
import './diag.vim'
import './selection.vim'
import './symbol.vim'
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>
if reply->has_key('error') && handleError
# request failed
- var emsg: string = reply.error.message
- emsg ..= $', code = {reply.error.code}'
- if reply.error->has_key('data')
- emsg ..= $', data = {reply.error.data->string()}'
- endif
- util.ErrMsg($'request {method} failed ({emsg})')
+ ProcessLspServerError(method, reply.error)
endif
return {}
if reply->has_key('error')
# request failed
- var emsg: string
- emsg = $'{reply.error.message}, code = {reply.error.code}'
- if reply.error->has_key('data')
- emsg ..= $', data = {reply.error.data->string()}'
- endif
- util.ErrMsg($'request {method} failed ({emsg})')
+ ProcessLspServerError(method, reply.error)
return
endif
# 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 GetLspPosition(find_ident: bool): dict<number>
+def GetPosition(lspserver: dict<any>, find_ident: bool): dict<number>
var lnum: number = line('.') - 1
var col: number = charcol('.') - 1
var line = getline('.')
# Compute character index counting composing characters as separate
# characters
- return {line: lnum, character: util.GetCharIdxWithCompChar(line, col)}
+ 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 GetLspTextDocPosition(find_ident: bool): dict<dict<any>>
+def GetTextDocPosition(lspserver: dict<any>, find_ident: bool): dict<dict<any>>
# interface TextDocumentIdentifier
# interface Position
return {textDocument: {uri: util.LspFileToUri(@%)},
- position: GetLspPosition(find_ident)}
+ position: lspserver.getPosition(find_ident)}
enddef
# Get a list of completion items.
# interface CompletionParams
# interface TextDocumentPositionParams
- var params = GetLspTextDocPosition(false)
+ var params = lspserver.getTextDocPosition(false)
# interface CompletionContext
params.context = {triggerKind: triggerKind_arg, triggerCharacter: triggerChar}
# Result: Location | Location[] | LocationLink[] | null
def GotoSymbolLoc(lspserver: dict<any>, msg: string, peekSymbol: bool,
cmdmods: string, count: number)
- var reply = lspserver.rpc(msg, GetLspTextDocPosition(true), false)
+ var reply = lspserver.rpc(msg, lspserver.getTextDocPosition(true), false)
if reply->empty() || reply.result->empty()
var emsg: string
if msg == 'textDocument/declaration'
return
endif
+ var result = reply.result
var location: dict<any>
- if reply.result->type() == v:t_list
+ 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 reply.result->len() > 1
+ if result->len() > 1
var title: string = ''
if msg == 'textDocument/declaration'
title = 'Declarations'
title = 'Definitions'
endif
- symbol.ShowLocations(lspserver, reply.result, peekSymbol, title)
+ 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 >= reply.result->len()
- idx = reply.result->len() - 1
+ if idx >= result->len()
+ idx = result->len() - 1
endif
- location = reply.result[idx]
+ location = result[idx]
else
- location = reply.result
+ location = result
endif
+ lspserver.decodeLocation(location)
symbol.GotoSymbol(lspserver, location, peekSymbol, cmdmods)
enddef
# interface SignatureHelpParams
# interface TextDocumentPositionParams
- var params = GetLspTextDocPosition(false)
+ var params = lspserver.getTextDocPosition(false)
lspserver.rpc_a('textDocument/signatureHelp', params,
signature.SignatureHelp)
enddef
# interface HoverParams
# interface TextDocumentPositionParams
- var params = GetLspTextDocPosition(false)
+ var params = lspserver.getTextDocPosition(false)
lspserver.rpc_a('textDocument/hover', params, (_, reply) => {
hover.HoverReply(lspserver, reply, cmdmods)
})
# interface ReferenceParams
# interface TextDocumentPositionParams
var param: dict<any>
- param = GetLspTextDocPosition(true)
+ param = lspserver.getTextDocPosition(true)
param.context = {includeDeclaration: true}
var reply = lspserver.rpc('textDocument/references', param)
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
endif
for docHL in docHighlightReply
+ lspserver.decodeRange(bnr, docHL.range)
var kind: number = docHL->get('kind', 1)
var propName: string
if kind == 2
# interface DocumentHighlightParams
# interface TextDocumentPositionParams
- var params = GetLspTextDocPosition(false)
+ var params = lspserver.getTextDocPosition(false)
lspserver.rpc_a('textDocument/documentHighlight', params, (_, reply) => {
DocHighlightReply(lspserver, reply, bufnr(), cmdmods)
})
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()
# interface CallHierarchyPrepareParams
# interface TextDocumentPositionParams
var param: dict<any>
- param = GetLspTextDocPosition(false)
+ param = lspserver.getTextDocPosition(false)
var reply = lspserver.rpc('textDocument/prepareCallHierarchy', param)
if reply->empty() || reply.result->empty()
return {}
if reply->empty()
return null
endif
+
+ if lspserver.needOffsetEncoding
+ # Decode the position encoding in all the incoming call locations
+ var bnr = util.LspUriToBufnr(item.uri)
+ reply.result->map((_, hierItem) => {
+ lspserver.decodeRange(bnr, hierItem.from.range)
+ return hierItem
+ })
+ endif
+
return reply.result
enddef
if reply->empty()
return null
endif
+
+ if lspserver.needOffsetEncoding
+ # Decode the position encoding in all the outgoing call locations
+ var bnr = util.LspUriToBufnr(item.uri)
+ reply.result->map((_, hierItem) => {
+ lspserver.decodeRange(bnr, hierItem.to.range)
+ return hierItem
+ })
+ endif
+
return reply.result
enddef
}
}
+ lspserver.encodeRange(bufnr(), param.range)
+
var msg: string
if lspserver.isClangdInlayHintsProvider
# clangd-style inlay hints
var reply = lspserver.rpc_a(msg, param, inlayhints.InlayHintsReply)
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
# interface TypeHierarchy
# interface TextDocumentPositionParams
var param: dict<any>
- param = GetLspTextDocPosition(false)
+ param = lspserver.getTextDocPosition(false)
# 0: children, 1: parent, 2: both
param.direction = direction
param.resolve = 5
return
endif
- typehier.ShowTypeHierarchy(lspserver, direction == 1, reply.result)
+ 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"
# interface RenameParams
# interface TextDocumentPositionParams
var param: dict<any> = {}
- param = GetLspTextDocPosition(true)
+ param = lspserver.getTextDocPosition(true)
param.newName = newName
var reply = lspserver.rpc('textDocument/rename', param)
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,
var r: dict<dict<number>> = {
start: {
line: line1 - 1,
- character: line1 == line2 ? charcol('.') - 1 : 0
+ character: line1 == line2 ? util.GetCharIdxWithCompChar(getline('.'), charcol('.') - 1) : 0
},
end: {
line: line2 - 1,
- character: charcol([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)
+ 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}})
return
endif
+ DecodeCodeAction(lspserver, reply.result)
+
codeaction.ApplyCodeAction(lspserver, reply.result, query)
enddef
return
endif
- codelens.ProcessCodeLens(lspserver, reply.result)
+ 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>, codeLens: dict<any>): dict<any>
+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
- return reply.result
+
+ 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
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
var param = {}
param.textDocument = {}
param.textDocument.uri = util.LspFileToUri(fname)
- param.positions = [GetLspPosition(false)]
+ param.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
# interface DefinitionParams
# interface TextDocumentPositionParams
- var reply = lspserver.rpc('textDocument/definition', GetLspTextDocPosition(false))
+ var reply = lspserver.rpc('textDocument/definition',
+ lspserver.getTextDocPosition(false))
if reply->empty() || reply.result->empty()
return null
endif
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
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]),
textdocDidOpen: function(TextdocDidOpen, [lspserver]),
textdocDidClose: function(TextdocDidClose, [lspserver]),
textdocDidChange: function(TextdocDidChange, [lspserver]),
--- /dev/null
+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
+
+ bnr->bufload()
+ var text = bnr->getbufline(pos.line + 1)->get(0, '')
+ if text == ''
+ 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
+
+ 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 == ''
+ 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
# interface SymbolInformation
fileName = util.LspUriToFile(symbol.location.uri)
r = symbol.location.range
+ lspserver.decodeRange(fileName->bufnr(), r)
symName = symbol.name
if symbol->has_key('containerName') && symbol.containerName != ''
enddef
# process SymbolInformation[]
-def ProcessSymbolInfoTable(symbolInfoTable: list<dict<any>>,
- symbolTypeTable: dict<list<dict<any>>>,
- symbolLineTable: list<dict<any>>)
+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
endif
endif
r = syminfo.location.range
+ lspserver.decodeRange(bnr, r)
if !symbolTypeTable->has_key(symbolType)
symbolTypeTable[symbolType] = []
enddef
# process DocumentSymbol[]
-def ProcessDocSymbolTable(docSymbolTable: list<dict<any>>,
- symbolTypeTable: dict<list<dict<any>>>,
- symbolLineTable: list<dict<any>>)
+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>>
for syminfo in docSymbolTable
name = syminfo.name
symbolType = SymbolKindToName(syminfo.kind)
- r = syminfo.range
+ r = syminfo.selectionRange
+ lspserver.decodeRange(bnr, r)
if syminfo->has_key('detail')
symbolDetail = syminfo.detail
endif
endif
childSymbols = {}
if syminfo->has_key('children')
- ProcessDocSymbolTable(syminfo.children, childSymbols, symbolLineTable)
+ ProcessDocSymbolTable(lspserver, bnr, syminfo.children, childSymbols,
+ symbolLineTable)
endif
symInfo = {name: name, range: r, detail: symbolDetail,
children: childSymbols}
export def DocSymbolReply(lspserver: dict<any>, docsymbol: any, fname: string)
var symbolTypeTable: dict<list<dict<any>>> = {}
var symbolLineTable: list<dict<any>> = []
+ var bnr = fname->bufnr()
if docsymbol->empty()
# No symbols defined for this file. Clear the outline window.
if docsymbol[0]->has_key('location')
# SymbolInformation[]
- ProcessSymbolInfoTable(docsymbol, symbolTypeTable, symbolLineTable)
+ ProcessSymbolInfoTable(lspserver, bnr, docsymbol, symbolTypeTable,
+ symbolLineTable)
else
# DocumentSymbol[]
- ProcessDocSymbolTable(docsymbol, symbolTypeTable, symbolLineTable)
+ ProcessDocSymbolTable(lspserver, bnr, docsymbol, symbolTypeTable,
+ symbolLineTable)
endif
# sort the symbols by line number
path complete path to the language server executable
(without any arguments).
*lsp-cfg-args*
- args a list of command-line arguments passed to the
- language server. Each argument is a separate List
- item.
+ args a |List| of command-line arguments passed to the
+ language server. Each space separated language server
+ command-line argument is a separate List item.
*lsp-cfg-filetype*
filetype One or more file types supported by the language
server. This can be a |String| or a |List|. To
--- /dev/null
+vim9script
+# Unit tests for language server protocol offset encoding using clangd
+
+source common.vim
+
+# Start the C language server. Returns true on success and false on failure.
+def g:StartLangServer(): bool
+ if has('patch-9.0.1629')
+ return g:StartLangServerWithFile('Xtest.c')
+ endif
+ return false
+enddef
+
+if !has('patch-9.0.1629')
+ # Need patch 9.0.1629 to properly encode/decode the UTF-16 offsets
+ finish
+endif
+
+var lspOpts = {autoComplete: false, highlightDiagInline: true}
+g:LspOptionsSet(lspOpts)
+
+var lspServers = [{
+ filetype: ['c', 'cpp'],
+ path: (exepath('clangd-15') ?? exepath('clangd')),
+ args: ['--background-index',
+ '--clang-tidy',
+ $'--offset-encoding={$LSP_OFFSET_ENCODING}']
+ }]
+call LspAddServer(lspServers)
+
+# Test for :LspCodeAction with symbols containing multibyte and composing
+# characters
+def g:Test_LspCodeAction_multibyte()
+ silent! edit XLspCodeAction_mb.c
+ sleep 200m
+ var lines =<< trim END
+ #include <stdio.h>
+ void fn(int aVar)
+ {
+ printf("aVar = %d\n", aVar);
+ printf("馃槉馃槉馃槉馃槉 = %d\n", aVar):
+ printf("a虂b虂a虂b虂 = %d\n", aVar):
+ printf("a台虂a台虂a台虂a台虂 = %d\n", aVar):
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(3)
+ :redraw!
+ cursor(5, 5)
+ redraw!
+ :LspCodeAction 1
+ assert_equal(' printf("馃槉馃槉馃槉馃槉 = %d\n", aVar);', getline(5))
+ cursor(6, 5)
+ redraw!
+ :LspCodeAction 1
+ assert_equal(' printf("a虂b虂a虂b虂 = %d\n", aVar);', getline(6))
+ cursor(7, 5)
+ redraw!
+ :LspCodeAction 1
+ assert_equal(' printf("a台虂a台虂a台虂a台虂 = %d\n", aVar);', getline(7))
+
+ :%bw!
+enddef
+
+# Test for :LspDiagShow when using multibyte and composing characters
+def g:Test_LspDiagShow_multibyte()
+ :silent! edit XLspDiagShow_mb.c
+ sleep 200m
+ var lines =<< trim END
+ #include <stdio.h>
+ void fn(int aVar)
+ {
+ printf("aVar = %d\n", aVar);
+ printf("馃槉馃槉馃槉馃槉 = %d\n". aVar);
+ printf("a虂b虂a虂b虂 = %d\n". aVar);
+ printf("a台虂a台虂a台虂a台虂 = %d\n". aVar);
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(3)
+ :redraw!
+ :LspDiagShow
+ var qfl: list<dict<any>> = getloclist(0)
+ assert_equal([5, 37], [qfl[0].lnum, qfl[0].col])
+ assert_equal([6, 33], [qfl[1].lnum, qfl[1].col])
+ assert_equal([7, 41], [qfl[2].lnum, qfl[2].col])
+ :lclose
+ :%bw!
+enddef
+
+# Test for :LspFormat when using multibyte and composing characters
+def g:Test_LspFormat_multibyte()
+ :silent! edit XLspFormat_mb.c
+ sleep 200m
+ var lines =<< trim END
+ void fn(int aVar)
+ {
+ int 馃槉馃槉馃槉馃槉 = aVar + 1;
+ int a虂b虂a虂b虂 = aVar + 1;
+ int a台虂a台虂a台虂a台虂 = aVar + 1;
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ :redraw!
+ :LspFormat
+ var expected =<< trim END
+ void fn(int aVar) {
+ int 馃槉馃槉馃槉馃槉 = aVar + 1;
+ int a虂b虂a虂b虂 = aVar + 1;
+ int a台虂a台虂a台虂a台虂 = aVar + 1;
+ }
+ END
+ assert_equal(expected, getline(1, '$'))
+ :%bw!
+enddef
+
+# Test for :LspGotoDefinition when using multibyte and composing characters
+def g:Test_LspGotoDefinition_multibyte()
+ :silent! edit XLspGotoDefinition_mb.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ #include <stdio.h>
+ void fn(int aVar)
+ {
+ printf("aVar = %d\n", aVar);
+ printf("馃槉馃槉馃槉馃槉 = %d\n", aVar);
+ printf("a虂b虂a虂b虂 = %d\n", aVar);
+ printf("a台虂a台虂a台虂a台虂 = %d\n", aVar);
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ redraw!
+
+ for [lnum, colnr] in [[4, 27], [5, 39], [6, 35], [7, 43]]
+ cursor(lnum, colnr)
+ :LspGotoDefinition
+ assert_equal([2, 13], [line('.'), col('.')])
+ endfor
+
+ :%bw!
+enddef
+
+# Test for :LspGotoDefinition when using multibyte and composing characters
+def g:Test_LspGotoDefinition_after_multibyte()
+ :silent! edit XLspGotoDef_after_mb.c
+ sleep 200m
+ var lines =<< trim END
+ void fn(int aVar)
+ {
+ /* 伪尾纬未, 馃槉馃槉馃槉馃槉, a虂b虂a虂b虂, a台虂a台虂a台虂a台虂 */ int 伪尾纬未, bVar;
+ /* 伪尾纬未, 馃槉馃槉馃槉馃槉, a虂b虂a虂b虂, a台虂a台虂a台虂a台虂 */ int 馃槉馃槉馃槉馃槉, cVar;
+ /* 伪尾纬未, 馃槉馃槉馃槉馃槉, a虂b虂a虂b虂, a台虂a台虂a台虂a台虂 */ int a虂b虂a虂b虂, dVar;
+ /* 伪尾纬未, 馃槉馃槉馃槉馃槉, a虂b虂a虂b虂, a台虂a台虂a台虂a台虂 */ int a台虂a台虂a台虂a台虂, eVar;
+ bVar = 1;
+ cVar = 2;
+ dVar = 3;
+ eVar = 4;
+ aVar = 伪尾纬未 + 馃槉馃槉馃槉馃槉 + a虂b虂a虂b虂 + a台虂a台虂a台虂a台虂 + bVar;
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ :redraw!
+ cursor(7, 5)
+ :LspGotoDefinition
+ assert_equal([3, 88], [line('.'), col('.')])
+ cursor(8, 5)
+ :LspGotoDefinition
+ assert_equal([4, 96], [line('.'), col('.')])
+ cursor(9, 5)
+ :LspGotoDefinition
+ assert_equal([5, 92], [line('.'), col('.')])
+ cursor(10, 5)
+ :LspGotoDefinition
+ assert_equal([6, 100], [line('.'), col('.')])
+ cursor(11, 12)
+ :LspGotoDefinition
+ assert_equal([3, 78], [line('.'), col('.')])
+ cursor(11, 23)
+ :LspGotoDefinition
+ assert_equal([4, 78], [line('.'), col('.')])
+ cursor(11, 42)
+ :LspGotoDefinition
+ assert_equal([5, 78], [line('.'), col('.')])
+ cursor(11, 57)
+ :LspGotoDefinition
+ assert_equal([6, 78], [line('.'), col('.')])
+
+ :%bw!
+enddef
+
+# Test for doing omni completion for symbols with multibyte and composing
+# characters
+def g:Test_OmniComplete_multibyte()
+ :silent! edit XOmniComplete_mb.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ void Func1(void)
+ {
+ int 馃槉馃槉馃槉馃槉, aVar;
+ int a虂b虂a虂b虂, bVar;
+ int a台虂a台虂a台虂a台虂, cVar;
+
+
+
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ redraw!
+
+ cursor(6, 4)
+ feedkeys("aaV\<C-X>\<C-O> = 馃槉馃槉\<C-X>\<C-O>;", 'xt')
+ assert_equal(' aVar = 馃槉馃槉馃槉馃槉;', getline('.'))
+ cursor(7, 4)
+ feedkeys("abV\<C-X>\<C-O> = a虂b虂\<C-X>\<C-O>;", 'xt')
+ assert_equal(' bVar = a虂b虂a虂b虂;', getline('.'))
+ cursor(8, 4)
+ feedkeys("acV\<C-X>\<C-O> = a台虂a台虂\<C-X>\<C-O>;", 'xt')
+ assert_equal(' cVar = a台虂a台虂a台虂a台虂;', getline('.'))
+ feedkeys("oa虂b虂\<C-X>\<C-O> = a台虂a台虂\<C-X>\<C-O>;", 'xt')
+ assert_equal(' a虂b虂a虂b虂 = a台虂a台虂a台虂a台虂;', getline('.'))
+ feedkeys("oa台虂a台虂\<C-X>\<C-O> = a虂b虂\<C-X>\<C-O>;", 'xt')
+ assert_equal(' a台虂a台虂a台虂a台虂 = a虂b虂a虂b虂;', getline('.'))
+ :%bw!
+enddef
+
+# Test for :LspOutline with multibyte and composing characters
+def g:Test_Outline_multibyte()
+ silent! edit XLspOutline_mb.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ typedef void 馃槉馃槉馃槉馃槉;
+ typedef void a虂b虂a虂b虂;
+ typedef void a台虂a台虂a台虂a台虂;
+
+ 馃槉馃槉馃槉馃槉 Func1()
+ {
+ }
+
+ a虂b虂a虂b虂 Func2()
+ {
+ }
+
+ a台虂a台虂a台虂a台虂 Func3()
+ {
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ redraw!
+
+ cursor(1, 1)
+ :LspOutline
+ assert_equal(2, winnr('$'))
+
+ :wincmd w
+ cursor(5, 1)
+ feedkeys("\<CR>", 'xt')
+ assert_equal([2, 5, 18], [winnr(), line('.'), col('.')])
+
+ :wincmd w
+ cursor(6, 1)
+ feedkeys("\<CR>", 'xt')
+ assert_equal([2, 9, 14], [winnr(), line('.'), col('.')])
+
+ :wincmd w
+ cursor(7, 1)
+ feedkeys("\<CR>", 'xt')
+ assert_equal([2, 13, 22], [winnr(), line('.'), col('.')])
+
+ :wincmd w
+ cursor(10, 1)
+ feedkeys("\<CR>", 'xt')
+ assert_equal([2, 1, 14], [winnr(), line('.'), col('.')])
+
+ :wincmd w
+ cursor(11, 1)
+ feedkeys("\<CR>", 'xt')
+ assert_equal([2, 2, 14], [winnr(), line('.'), col('.')])
+
+ :wincmd w
+ cursor(12, 1)
+ feedkeys("\<CR>", 'xt')
+ assert_equal([2, 3, 14], [winnr(), line('.'), col('.')])
+
+ :%bw!
+enddef
+
+# Test for :LspRename with multibyte and composing characters
+def g:Test_LspRename_multibyte()
+ silent! edit XLspRename_mb.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ #include <stdio.h>
+ void fn(int aVar)
+ {
+ printf("aVar = %d\n", aVar);
+ printf("馃槉馃槉馃槉馃槉 = %d\n", aVar);
+ printf("a虂b虂a虂b虂 = %d\n", aVar);
+ printf("a台虂a台虂a台虂a台虂 = %d\n", aVar);
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ redraw!
+ cursor(2, 12)
+ :LspRename bVar
+ redraw!
+ var expected: list<string> =<< trim END
+ #include <stdio.h>
+ void fn(int bVar)
+ {
+ printf("aVar = %d\n", bVar);
+ printf("馃槉馃槉馃槉馃槉 = %d\n", bVar);
+ printf("a虂b虂a虂b虂 = %d\n", bVar);
+ printf("a台虂a台虂a台虂a台虂 = %d\n", bVar);
+ }
+ END
+ assert_equal(expected, getline(1, '$'))
+ :%bw!
+enddef
+
+# Test for :LspShowReferences when using multibyte and composing characters
+def g:Test_LspShowReferences_multibyte()
+ :silent! edit XLspShowReferences_mb.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ #include <stdio.h>
+ void fn(int aVar)
+ {
+ printf("aVar = %d\n", aVar);
+ printf("馃槉馃槉馃槉馃槉 = %d\n", aVar);
+ printf("a虂b虂a虂b虂 = %d\n", aVar);
+ printf("a台虂a台虂a台虂a台虂 = %d\n", aVar);
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ redraw!
+ cursor(4, 27)
+ :LspShowReferences
+ var qfl: list<dict<any>> = getloclist(0)
+ assert_equal([2, 13], [qfl[0].lnum, qfl[0].col])
+ assert_equal([4, 27], [qfl[1].lnum, qfl[1].col])
+ assert_equal([5, 39], [qfl[2].lnum, qfl[2].col])
+ assert_equal([6, 35], [qfl[3].lnum, qfl[3].col])
+ assert_equal([7, 43], [qfl[4].lnum, qfl[4].col])
+ :lclose
+
+ :%bw!
+enddef
+
+# Test for :LspSymbolSearch when using multibyte and composing characters
+def g:Test_LspSymbolSearch_multibyte()
+ silent! edit XLspSymbolSearch_mb.c
+ sleep 200m
+ var lines: list<string> =<< trim END
+ typedef void 馃槉馃槉馃槉馃槉;
+ typedef void a虂b虂a虂b虂;
+ typedef void a台虂a台虂a台虂a台虂;
+
+ 馃槉馃槉馃槉馃槉 Func1()
+ {
+ }
+
+ a虂b虂a虂b虂 Func2()
+ {
+ }
+
+ a台虂a台虂a台虂a台虂 Func3()
+ {
+ }
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+
+ cursor(1, 1)
+ feedkeys(":LspSymbolSearch Func1\<CR>\<CR>", "xt")
+ assert_equal([5, 18], [line('.'), col('.')])
+ cursor(1, 1)
+ feedkeys(":LspSymbolSearch Func2\<CR>\<CR>", "xt")
+ assert_equal([9, 14], [line('.'), col('.')])
+ cursor(1, 1)
+ feedkeys(":LspSymbolSearch Func3\<CR>\<CR>", "xt")
+ assert_equal([13, 22], [line('.'), col('.')])
+
+ :%bw!
+enddef
+
+# Test for setting the 'tagfunc' with multibyte and composing characters in
+# symbols
+def g:Test_LspTagFunc_multibyte()
+ var lines =<< trim END
+ void fn(int aVar)
+ {
+ int 馃槉馃槉馃槉馃槉, bVar;
+ int a虂b虂a虂b虂, cVar;
+ int a台虂a台虂a台虂a台虂, dVar;
+ bVar = 10;
+ cVar = 10;
+ dVar = 10;
+ }
+ END
+ writefile(lines, 'Xtagfunc_mb.c')
+ :silent! edit! Xtagfunc_mb.c
+ g:WaitForServerFileLoad(0)
+ :setlocal tagfunc=lsp#lsp#TagFunc
+ cursor(6, 5)
+ :exe "normal \<C-]>"
+ assert_equal([3, 27], [line('.'), col('.')])
+ cursor(7, 5)
+ :exe "normal \<C-]>"
+ assert_equal([4, 23], [line('.'), col('.')])
+ cursor(8, 5)
+ :exe "normal \<C-]>"
+ assert_equal([5, 31], [line('.'), col('.')])
+ :set tagfunc&
+
+ :%bw!
+ delete('Xtagfunc_mb.c')
+enddef
+
+# Test for the :LspSuperTypeHierarchy and :LspSubTypeHierarchy commands with
+# multibyte and composing characters
+def g:Test_LspTypeHier_multibyte()
+ silent! edit XLspTypeHier_mb.cpp
+ sleep 200m
+ var lines =<< trim END
+ /* 伪尾馃槉馃槉a虂a虂a台虂a台虂 */ class parent {
+ };
+
+ /* 伪尾馃槉馃槉a虂a虂a台虂a台虂 */ class child : public parent {
+ };
+
+ /* 伪尾馃槉馃槉a虂a虂a台虂a台虂 */ class grandchild : public child {
+ };
+ END
+ setline(1, lines)
+ g:WaitForServerFileLoad(0)
+ redraw!
+
+ cursor(1, 42)
+ :LspSubTypeHierarchy
+ call feedkeys("\<CR>", 'xt')
+ assert_equal([1, 36], [line('.'), col('.')])
+ cursor(1, 42)
+
+ :LspSubTypeHierarchy
+ call feedkeys("\<Down>\<CR>", 'xt')
+ assert_equal([4, 42], [line('.'), col('.')])
+
+ cursor(1, 42)
+ :LspSubTypeHierarchy
+ call feedkeys("\<Down>\<Down>\<CR>", 'xt')
+ assert_equal([7, 42], [line('.'), col('.')])
+
+ cursor(7, 42)
+ :LspSuperTypeHierarchy
+ call feedkeys("\<CR>", 'xt')
+ assert_equal([7, 36], [line('.'), col('.')])
+
+ cursor(7, 42)
+ :LspSuperTypeHierarchy
+ call feedkeys("\<Down>\<CR>", 'xt')
+ assert_equal([4, 36], [line('.'), col('.')])
+
+ cursor(7, 42)
+ :LspSuperTypeHierarchy
+ call feedkeys("\<Down>\<Down>\<CR>", 'xt')
+ assert_equal([1, 36], [line('.'), col('.')])
+
+ :%bw!
+enddef
+
+# vim: shiftwidth=2 softtabstop=2 noexpandtab
:%bw!
enddef
-# Test for :LspFormat when using composing characters
-def g:Test_LspFormat_ComposingChars()
- :silent! edit XLspFormatComposing.c
- sleep 200m
- var lines =<< trim END
- void fn(int aVar)
- {
- int 馃槉馃槉馃槉馃槉 = aVar + 1;
- int a虂b虂a虂b虂 = aVar + 1;
- int a台虂a台虂a台虂a台虂 = aVar + 1;
- }
- END
- setline(1, lines)
- g:WaitForServerFileLoad(0)
- :redraw!
- :LspFormat
- var expected =<< trim END
- void fn(int aVar) {
- int 馃槉馃槉馃槉馃槉 = aVar + 1;
- int a虂b虂a虂b虂 = aVar + 1;
- int a台虂a台虂a台虂a台虂 = aVar + 1;
- }
- END
- assert_equal(expected, getline(1, '$'))
- :%bw!
-enddef
-
# Test for formatting a file using 'formatexpr'
def g:Test_LspFormatExpr()
:silent! edit XLspFormat.c
:%bw!
enddef
-# Test for :LspShowReferences when using composing characters
-def g:Test_LspShowReferences_ComposingChars()
- :silent! edit Xtest.c
- sleep 200m
- var lines: list<string> =<< trim END
- #include <stdio.h>
- void fn(int aVar)
- {
- printf("aVar = %d\n", aVar);
- printf("馃槉馃槉馃槉馃槉 = %d\n", aVar);
- printf("a虂b虂a虂b虂 = %d\n", aVar);
- printf("a台虂a台虂a台虂a台虂 = %d\n", aVar);
- }
- END
- setline(1, lines)
- g:WaitForServerFileLoad(0)
- redraw!
- cursor(4, 27)
- :LspShowReferences
- var qfl: list<dict<any>> = getloclist(0)
- assert_equal([2, 13], [qfl[0].lnum, qfl[0].col])
- assert_equal([4, 27], [qfl[1].lnum, qfl[1].col])
- assert_equal([5, 39], [qfl[2].lnum, qfl[2].col])
- assert_equal([6, 35], [qfl[3].lnum, qfl[3].col])
- assert_equal([7, 43], [qfl[4].lnum, qfl[4].col])
- :lclose
-
- :%bw!
-enddef
-
# Test for LSP diagnostics
def g:Test_LspDiag()
:silent! edit XLspDiag.c
:%bw!
enddef
-# Test for :LspDiagShow when using composing characters
-def g:Test_LspDiagShow_ComposingChars()
- :silent! edit XDiagShowCompose.c
- sleep 200m
- var lines =<< trim END
- #include <stdio.h>
- void fn(int aVar)
- {
- printf("aVar = %d\n", aVar);
- printf("馃槉馃槉馃槉馃槉 = %d\n". aVar);
- printf("a虂b虂a虂b虂 = %d\n". aVar);
- printf("a台虂a台虂a台虂a台虂 = %d\n". aVar);
- }
- END
- setline(1, lines)
- g:WaitForServerFileLoad(3)
- :redraw!
- :LspDiagShow
- var qfl: list<dict<any>> = getloclist(0)
- assert_equal([5, 37], [qfl[0].lnum, qfl[0].col])
- assert_equal([6, 33], [qfl[1].lnum, qfl[1].col])
- assert_equal([7, 41], [qfl[2].lnum, qfl[2].col])
- :lclose
- :%bw!
-enddef
-
# Test for LSP diagnostics handler
def g:Test_LspProcessDiagHandler()
g:LSPTest_modifyDiags = true
:%bw!
enddef
-# Test for :LspCodeAction with symbols containing composing characters
-def g:Test_LspCodeAction_ComposingChars()
- silent! edit XLspCodeActionComposing.c
- sleep 200m
- var lines =<< trim END
- #include <stdio.h>
- void fn(int aVar)
- {
- printf("aVar = %d\n", aVar);
- printf("馃槉馃槉馃槉馃槉 = %d\n", aVar):
- printf("a虂b虂a虂b虂 = %d\n", aVar):
- printf("a台虂a台虂a台虂a台虂 = %d\n", aVar):
- }
- END
- setline(1, lines)
- g:WaitForServerFileLoad(3)
- :redraw!
- cursor(5, 5)
- redraw!
- :LspCodeAction 1
- assert_equal(' printf("馃槉馃槉馃槉馃槉 = %d\n", aVar);', getline(5))
- cursor(6, 5)
- redraw!
- :LspCodeAction 1
- assert_equal(' printf("a虂b虂a虂b虂 = %d\n", aVar);', getline(6))
- cursor(7, 5)
- redraw!
- :LspCodeAction 1
- assert_equal(' printf("a台虂a台虂a台虂a台虂 = %d\n", aVar);', getline(7))
-
- :%bw!
-enddef
-
# Test for :LspRename
def g:Test_LspRename()
silent! edit XLspRename.c
:%bw!
enddef
-# Test for :LspRename with composing characters
-def g:Test_LspRename_ComposingChars()
- silent! edit XLspRenameComposing.c
- sleep 200m
- var lines: list<string> =<< trim END
- #include <stdio.h>
- void fn(int aVar)
- {
- printf("aVar = %d\n", aVar);
- printf("馃槉馃槉馃槉馃槉 = %d\n", aVar);
- printf("a虂b虂a虂b虂 = %d\n", aVar);
- printf("a台虂a台虂a台虂a台虂 = %d\n", aVar);
- }
- END
- setline(1, lines)
- g:WaitForServerFileLoad(0)
- redraw!
- cursor(2, 12)
- :LspRename bVar
- redraw!
- var expected: list<string> =<< trim END
- #include <stdio.h>
- void fn(int bVar)
- {
- printf("aVar = %d\n", bVar);
- printf("馃槉馃槉馃槉馃槉 = %d\n", bVar);
- printf("a虂b虂a虂b虂 = %d\n", bVar);
- printf("a台虂a台虂a台虂a台虂 = %d\n", bVar);
- }
- END
- assert_equal(expected, getline(1, '$'))
- :%bw!
-enddef
-
# Test for :LspSelectionExpand and :LspSelectionShrink
def g:Test_LspSelection()
silent! edit XLspSelection.c
:%bw!
enddef
-# Test for :LspGotoDefinition when using composing characters
-def g:Test_LspGotoDefinition_With_ComposingCharacters()
- :silent! edit Xtest.c
- sleep 200m
- var lines: list<string> =<< trim END
- #include <stdio.h>
- void fn(int aVar)
- {
- printf("aVar = %d\n", aVar);
- printf("馃槉馃槉馃槉馃槉 = %d\n", aVar);
- printf("a虂b虂a虂b虂 = %d\n", aVar);
- printf("a台虂a台虂a台虂a台虂 = %d\n", aVar);
- }
- END
- setline(1, lines)
- g:WaitForServerFileLoad(0)
- redraw!
-
- for [lnum, colnr] in [[4, 27], [5, 39], [6, 35], [7, 43]]
- cursor(lnum, colnr)
- :LspGotoDefinition
- assert_equal([2, 13], [line('.'), col('.')])
- endfor
-
- :%bw!
-enddef
-
-# Test for :LspGotoDefinition when using composing characters
-def g:Test_LspGotoDefinition_After_ComposingCharacters()
- :silent! edit Xtest.c
- sleep 200m
- var lines =<< trim END
- void fn(int aVar)
- {
- int 馃槉馃槉馃槉馃槉, bVar;
- int a虂b虂a虂b虂, cVar;
- int a台虂a台虂a台虂a台虂, dVar;
- bVar = 10;
- cVar = 10;
- dVar = 10;
- }
- END
- setline(1, lines)
- g:WaitForServerFileLoad(0)
- :redraw!
- cursor(6, 5)
- :LspGotoDefinition
- assert_equal([3, 27], [line('.'), col('.')])
- cursor(7, 5)
- :LspGotoDefinition
- assert_equal([4, 23], [line('.'), col('.')])
- cursor(8, 5)
- :LspGotoDefinition
- assert_equal([5, 31], [line('.'), col('.')])
-
- :%bw!
-enddef
-
# Test for :LspHighlight
def g:Test_LspHighlight()
silent! edit XLspHighlight.c
:%bw!
enddef
-# Test for :LspSymbolSearch when using composing characters
-def g:Test_LspSymbolSearch_ComposingChars()
- silent! edit XLspSymbolSearchCompose.c
- sleep 200m
- var lines: list<string> =<< trim END
- typedef void 馃槉馃槉馃槉馃槉;
- typedef void a虂b虂a虂b虂;
- typedef void a台虂a台虂a台虂a台虂;
-
- 馃槉馃槉馃槉馃槉 Func1()
- {
- }
-
- a虂b虂a虂b虂 Func2()
- {
- }
-
- a台虂a台虂a台虂a台虂 Func3()
- {
- }
- END
- setline(1, lines)
- g:WaitForServerFileLoad(0)
-
- cursor(1, 1)
- feedkeys(":LspSymbolSearch Func1\<CR>\<CR>", "xt")
- assert_equal([5, 18], [line('.'), col('.')])
- cursor(1, 1)
- feedkeys(":LspSymbolSearch Func2\<CR>\<CR>", "xt")
- assert_equal([9, 14], [line('.'), col('.')])
- cursor(1, 1)
- feedkeys(":LspSymbolSearch Func3\<CR>\<CR>", "xt")
- assert_equal([13, 22], [line('.'), col('.')])
-
- :%bw!
-enddef
-
# Test for :LspIncomingCalls
def g:Test_LspIncomingCalls()
silent! edit XLspIncomingCalls.c
delete('Xtagfunc.c')
enddef
-# Test for setting the 'tagfunc' with composing characters in symbols
-def g:Test_LspTagFunc_ComposingChars()
- var lines =<< trim END
- void fn(int aVar)
- {
- int 馃槉馃槉馃槉馃槉, bVar;
- int a虂b虂a虂b虂, cVar;
- int a台虂a台虂a台虂a台虂, dVar;
- bVar = 10;
- cVar = 10;
- dVar = 10;
- }
- END
- writefile(lines, 'XtagfuncCompose.c')
- :silent! edit! XtagfuncCompose.c
- g:WaitForServerFileLoad(0)
- :setlocal tagfunc=lsp#lsp#TagFunc
- cursor(6, 5)
- :exe "normal \<C-]>"
- assert_equal([3, 27], [line('.'), col('.')])
- cursor(7, 5)
- :exe "normal \<C-]>"
- assert_equal([4, 23], [line('.'), col('.')])
- cursor(8, 5)
- :exe "normal \<C-]>"
- assert_equal([5, 31], [line('.'), col('.')])
- :set tagfunc&
-
- :%bw!
- delete('XtagfuncCompose.c')
-enddef
-
# Test for the LspDiagsUpdated autocmd
def g:Test_LspDiagsUpdated_Autocmd()
g:LspAutoCmd = 0
:%bw!
enddef
-# Test for doing omni completion for symbols with composing characters
-def g:Test_OmniComplete_ComposingChars()
- :silent! edit XOmniCompleteCompose.c
- sleep 200m
- var lines: list<string> =<< trim END
- void Func1(void)
- {
- int 馃槉馃槉馃槉馃槉, aVar;
- int a虂b虂a虂b虂, bVar;
- int a台虂a台虂a台虂a台虂, cVar;
-
-
-
- }
- END
- setline(1, lines)
- g:WaitForServerFileLoad(0)
- redraw!
-
- cursor(6, 4)
- feedkeys("aaV\<C-X>\<C-O> = 馃槉馃槉\<C-X>\<C-O>;", 'xt')
- assert_equal(' aVar = 馃槉馃槉馃槉馃槉;', getline('.'))
- cursor(7, 4)
- feedkeys("abV\<C-X>\<C-O> = a虂b虂\<C-X>\<C-O>;", 'xt')
- assert_equal(' bVar = a虂b虂a虂b虂;', getline('.'))
- cursor(8, 4)
- feedkeys("acV\<C-X>\<C-O> = a台虂a台虂\<C-X>\<C-O>;", 'xt')
- assert_equal(' cVar = a台虂a台虂a台虂a台虂;', getline('.'))
- feedkeys("oa虂b虂\<C-X>\<C-O> = a台虂a台虂\<C-X>\<C-O>;", 'xt')
- assert_equal(' a虂b虂a虂b虂 = a台虂a台虂a台虂a台虂;', getline('.'))
- feedkeys("oa台虂a台虂\<C-X>\<C-O> = a虂b虂\<C-X>\<C-O>;", 'xt')
- assert_equal(' a台虂a台虂a台虂a台虂 = a虂b虂a虂b虂;', getline('.'))
- :%bw!
-enddef
-
# Test for the :LspServer command.
def g:Test_LspServer()
new a.raku
TESTS="clangd_tests.vim tsserver_tests.vim gopls_tests.vim not_lspserver_related_tests.vim"
-for testfile in $TESTS
-do
+RunTestsInFile() {
+ testfile=$1
echo "Running tests in $testfile"
$VIM_CMD -c "let g:TestName='$testfile'" -S runner.vim
echo "SUCCESS: All the tests in $testfile passed."
echo
+}
+
+for testfile in $TESTS
+do
+ RunTestsInFile $testfile
+done
+
+for encoding in "utf-8" "utf-16" "utf-32"
+do
+ export LSP_OFFSET_ENCODING=$encoding
+ echo "LSP offset encoding: $LSP_OFFSET_ENCODING"
+ RunTestsInFile clangd_offsetencoding.vim
done
echo "SUCCESS: All the tests passed."