autoload/lsp/lspserver.vim | 45 +++++++++++++++++++++++++++++++-------------- test/unit_tests.vim | 41 +++++++++++++++++++++++++++++++++++++++++ diff --git a/autoload/lsp/lspserver.vim b/autoload/lsp/lspserver.vim index f0397c8dfd90321090bfdf35e9ebb855388df9fa..28d9813366cf25d150b61037fb133e5676980f8d 100644 --- a/autoload/lsp/lspserver.vim +++ b/autoload/lsp/lspserver.vim @@ -789,22 +789,39 @@ 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 GetLspPosition(): dict +def GetLspPosition(find_ident: bool): dict 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 + return {line: lnum, character: col} enddef # Return the current file name and current cursor position as a LSP # TextDocumentPositionParams structure -def GetLspTextDocPosition(): dict> +def GetLspTextDocPosition(find_ident: bool): dict> # interface TextDocumentIdentifier # interface Position return {textDocument: {uri: util.LspFileToUri(@%)}, - position: GetLspPosition()} + position: GetLspPosition(find_ident)} enddef # Get a list of completion items. @@ -824,7 +841,7 @@ endif # interface CompletionParams # interface TextDocumentPositionParams - var params = GetLspTextDocPosition() + var params = GetLspTextDocPosition(false) # interface CompletionContext params.context = {triggerKind: triggerKind_arg, triggerCharacter: triggerChar} @@ -866,7 +883,7 @@ # # Result: Location | Location[] | LocationLink[] | null def GotoSymbolLoc(lspserver: dict, msg: string, peekSymbol: bool, cmdmods: string) - var reply = lspserver.rpc(msg, GetLspTextDocPosition()) + var reply = lspserver.rpc(msg, GetLspTextDocPosition(true)) if reply->empty() || reply.result->empty() var emsg: string if msg ==# 'textDocument/declaration' @@ -984,7 +1001,7 @@ endif # interface SignatureHelpParams # interface TextDocumentPositionParams - var params = GetLspTextDocPosition() + var params = GetLspTextDocPosition(false) lspserver.rpc_a('textDocument/signatureHelp', params, signature.SignatureHelp) enddef @@ -1017,7 +1034,7 @@ endif # interface HoverParams # interface TextDocumentPositionParams - var params = GetLspTextDocPosition() + var params = GetLspTextDocPosition(false) lspserver.rpc_a('textDocument/hover', params, hover.HoverReply) enddef @@ -1033,7 +1050,7 @@ # interface ReferenceParams # interface TextDocumentPositionParams var param: dict - param = GetLspTextDocPosition() + param = GetLspTextDocPosition(true) param.context = {includeDeclaration: true} var reply = lspserver.rpc('textDocument/references', param) @@ -1090,7 +1107,7 @@ endif # interface DocumentHighlightParams # interface TextDocumentPositionParams - var params = GetLspTextDocPosition() + var params = GetLspTextDocPosition(false) lspserver.rpc_a('textDocument/documentHighlight', params, function('DocHighlightReply', [bufnr()])) enddef @@ -1176,7 +1193,7 @@ def PrepareCallHierarchy(lspserver: dict): dict # interface CallHierarchyPrepareParams # interface TextDocumentPositionParams var param: dict - param = GetLspTextDocPosition() + param = GetLspTextDocPosition(false) var reply = lspserver.rpc('textDocument/prepareCallHierarchy', param) if reply->empty() || reply.result->empty() return {} @@ -1287,7 +1304,7 @@ # interface TypeHierarchy # interface TextDocumentPositionParams var param: dict - param = GetLspTextDocPosition() + param = GetLspTextDocPosition(false) # 0: children, 1: parent, 2: both param.direction = direction param.resolve = 5 @@ -1312,7 +1329,7 @@ # interface RenameParams # interface TextDocumentPositionParams var param: dict = {} - param = GetLspTextDocPosition() + param = GetLspTextDocPosition(true) param.newName = newName var reply = lspserver.rpc('textDocument/rename', param) @@ -1453,7 +1470,7 @@ # interface TextDocumentIdentifier var param = {} param.textDocument = {} param.textDocument.uri = util.LspFileToUri(fname) - param.positions = [GetLspPosition()] + param.positions = [GetLspPosition(false)] var reply = lspserver.rpc('textDocument/selectionRange', param) if reply->empty() || reply.result->empty() @@ -1578,7 +1595,7 @@ endif # interface DefinitionParams # interface TextDocumentPositionParams - var reply = lspserver.rpc('textDocument/definition', GetLspTextDocPosition()) + var reply = lspserver.rpc('textDocument/definition', GetLspTextDocPosition(false)) if reply->empty() || reply.result->empty() return null endif diff --git a/test/unit_tests.vim b/test/unit_tests.vim index cbdf008db332050611553aea260ca1ca9a7e85fa..d3d0d45216dbc53df2159ba59bb8bd8e7ba39bf1 100644 --- a/test/unit_tests.vim +++ b/test/unit_tests.vim @@ -979,6 +979,47 @@ WaitForAssert(() => assert_equal(true, g:LSPTest_customNotificationHandlerReplied)) :%bw! enddef +def Test_ScanFindIdent() + :silent! edit Xtest.c + sleep 200m + var lines: list =<< trim END + int count; + int fn(int a) + { + int hello; + hello = a; + return count + 1; + } + END + setline(1, lines) + :redraw! + + # LspGotoDefinition et al + cursor(5, 10) + assert_equal([], + execute('LspGotoDefinition')->split("\n")) + assert_equal([2, 12], [line('.'), col('.')]) + + cursor(6, 10) + assert_equal([], + execute('LspGotoDefinition')->split("\n")) + assert_equal([1, 5], [line('.'), col('.')]) + + # LspShowReferences + cursor(6, 10) + assert_equal([], + execute('LspShowReferences')->split("\n")) + + # LspRename + cursor(6, 10) + assert_equal([], + execute('LspRename counter')->split("\n")) + assert_equal('int counter;', getline(1)) + assert_equal(' return counter + 1;', getline(6)) + + bw! +enddef + # Start the C language server. Returns true on success and false on failure. def StartLangServer(): bool # Edit a dummy C file to start the LSP server