:LspGotoDeclaration|Go to the declaration of the keyword under cursor
:LspGotoTypeDef|Go to the type definition of the keyword under cursor
:LspGotoImpl|Go to the implementation of the keyword under cursor
+:LspPeekDefinition|Open the definition of the symbol under cursor in the preview window.
+:LspPeekDeclaration|Open the declaration of the symbol under cursor in the preview window.
+:LspPeekTypeDef|Open the type definition of the symbol under cursor in the preview window.
+:LspPeekImpl|Open the implementation of the symbol under cursor in the preview window.
:LspShowSignature|Display the signature of the keyword under cursor
:LspDiagShow|Display the diagnostics messages from the LSP server for the current buffer in a new location list.
:LspDiagFirst|Display the first diagnostic message for the current buffer
:LspDiagPrev|Display the previous diagnostic message before the current line
:LspDiagCurrent|Display the diagnostic message for the current line
:LspShowReferences|Display the list of references to the keyword under cursor in a new location list.
+:LspPeekReferences|Display the list of references to the keyword under cursor in a location list associated with the preview window.
:LspHighlight|Highlight all the matches for the keyword under cursor
:LspHighlightClear|Clear all the matches highlighted by :LspHighlight
:LspOutline|Show the list of symbols defined in the current file in a separate window.
:LspRename|Rename the current symbol
:LspCodeAction|Apply the code action supplied by the LSP server to the diagnostic in the current line.
:LspSymbolSearch|Perform a workspace wide search for a symbol
-:LspSelectionRange|Visually select the current symbol range
+:LspSelectionExpand|Expand the current symbol range visual selection
+:LspSelectionShrink|Shrink the current symbol range visual selection
:LspFold|Fold the current file
:LspWorkspaceAddFolder `{folder}`| Add a folder to the workspace
:LspWorkspaceRemoveFolder `{folder}`|Remove a folder from the workspace
vim9script
+# Functions related to handling LSP code actions to fix diagnostics.
+
var util = {}
var textedit = {}
vim9script
+# Functions related to handling LSP diagnostics.
+
var opt = {}
var util = {}
var symbol = {}
var codeaction = {}
var callhier = {}
+var selection = {}
if has('patch-8.2.4019')
import './lspoptions.vim' as opt_import
import './symbol.vim' as symbol_import
import './codeaction.vim' as codeaction_import
import './callhierarchy.vim' as callhierarchy_import
+ import './selection.vim' as selection_import
opt.lspOptions = opt_import.lspOptions
util.WarnMsg = util_import.WarnMsg
codeaction.ApplyCodeAction = codeaction_import.ApplyCodeAction
callhier.IncomingCalls = callhierarchy_import.IncomingCalls
callhier.OutgoingCalls = callhierarchy_import.OutgoingCalls
+ selection.SelectionStart = selection_import.SelectionStart
else
import lspOptions from './lspoptions.vim'
import {WarnMsg,
import {ShowReferences, GotoSymbol} from './symbol.vim'
import ApplyCodeAction from './codeaction.vim'
import {IncomingCalls, OutgoingCalls} from './callhierarchy.vim'
+ import {SelectionStart} from './selection.vim'
opt.lspOptions = lspOptions
util.WarnMsg = WarnMsg
codeaction.ApplyCodeAction = ApplyCodeAction
callhier.IncomingCalls = IncomingCalls
callhier.OutgoingCalls = OutgoingCalls
+ selection.SelectionStart = SelectionStart
endif
# process the 'initialize' method reply from the LSP server
# Reply: 'textDocument/selectionRange'
# Result: SelectionRange[] | null
def s:processSelectionRangeReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>)
- if reply.result->empty()
- return
- endif
-
- var r: dict<dict<number>> = reply.result[0].range
- var bnr: number = bufnr()
- var start_col: number = util.GetLineByteFromPos(bnr, r.start) + 1
- var end_col: number = util.GetLineByteFromPos(bnr, r.end)
-
- :normal! v"_y
- setcharpos("'<", [0, r.start.line + 1, start_col, 0])
- setcharpos("'>", [0, r.end.line + 1, end_col, 0])
- :normal! gv
+ selection.SelectionStart(lspserver, reply.result)
enddef
# Reply: 'textDocument/foldingRange'
lspserver.removeWorkspaceFolder(dirName)
enddef
-# visually select a range of positions around the current cursor.
-export def SelectionRange()
+# expand the previous selection or start a new selection
+export def SelectionExpand()
var lspserver: dict<any> = s:curbufGetServerChecked()
if lspserver->empty()
return
endif
- var fname: string = @%
- # TODO: Also support passing a range
- lspserver.selectionRange(fname)
+ lspserver.selectionExpand()
+enddef
+
+# shrink the previous selection or start a new selection
+export def SelectionShrink()
+ var lspserver: dict<any> = s:curbufGetServerChecked()
+ if lspserver->empty()
+ return
+ endif
+
+ lspserver.selectionShrink()
enddef
# fold the entire document
var handlers = {}
var diag = {}
var util = {}
+var selection = {}
if has('patch-8.2.4019')
import './handlers.vim' as handlers_import
import './util.vim' as util_import
import './diag.vim' as diag_import
+ import './selection.vim' as selection_import
+
handlers.ProcessReply = handlers_import.ProcessReply
handlers.ProcessNotif = handlers_import.ProcessNotif
handlers.ProcessRequest = handlers_import.ProcessRequest
util.LspFileToUri = util_import.LspFileToUri
util.PushCursorToTagStack = util_import.PushCursorToTagStack
diag.GetDiagByLine = diag_import.GetDiagByLine
+ selection.SelectionModify = selection_import.SelectionModify
else
import {ProcessReply,
ProcessNotif,
LspBufnrToUri,
LspFileToUri,
PushCursorToTagStack} from './util.vim'
+ import {SelectionModify} from './selection.vim'
+
handlers.ProcessReply = ProcessReply
handlers.ProcessNotif = ProcessNotif
handlers.ProcessRequest = ProcessRequest
util.LspFileToUri = LspFileToUri
util.PushCursorToTagStack = PushCursorToTagStack
diag.GetDiagByLine = GetDiagByLine
+ selection.SelectionModify = SelectionModify
endif
# LSP server standard output handler
# LSP server exit callback
def s:exit_cb(lspserver: dict<any>, job: job, status: number): void
util.WarnMsg("LSP server exited with status " .. status)
- lspserver.job = v:none
+ lspserver.job = v:null
lspserver.running = false
lspserver.ready = false
lspserver.requests = {}
lspserver.exitServer()
lspserver.job->job_stop()
- lspserver.job = v:none
+ lspserver.job = v:null
lspserver.running = false
lspserver.ready = false
lspserver.requests = {}
return
endif
+ # clear the previous selection reply
+ lspserver.selection = {}
+
var req = lspserver.createRequest('textDocument/selectionRange')
# interface SelectionRangeParams
# interface TextDocumentIdentifier
req.params->extend({textDocument: {uri: util.LspFileToUri(fname)}, positions: [s:getLspPosition()]})
lspserver.sendMessage(req)
+
+ s:waitForReponse(lspserver, req)
+enddef
+
+# Expand the previous selection or start a new one
+def s:selectionExpand(lspserver: dict<any>)
+ # Check whether LSP server supports selection ranges
+ if !lspserver.caps->has_key('selectionRangeProvider')
+ || !lspserver.caps.selectionRangeProvider
+ util.ErrMsg("Error: LSP server does not support selection ranges")
+ return
+ endif
+
+ selection.SelectionModify(lspserver, true)
+enddef
+
+# Shrink the previous selection or start a new one
+def s:selectionShrink(lspserver: dict<any>)
+ # Check whether LSP server supports selection ranges
+ if !lspserver.caps->has_key('selectionRangeProvider')
+ || !lspserver.caps.selectionRangeProvider
+ util.ErrMsg("Error: LSP server does not support selection ranges")
+ return
+ endif
+
+ selection.SelectionModify(lspserver, false)
enddef
# fold the entire document
workspaceSymbolPopup: 0,
workspaceSymbolQuery: '',
peekSymbol: false,
- callHierarchyType: ''
+ callHierarchyType: '',
+ selection: {}
}
# Add the LSP server functions
lspserver->extend({
addWorkspaceFolder: function('s:addWorkspaceFolder', [lspserver]),
removeWorkspaceFolder: function('s:removeWorkspaceFolder', [lspserver]),
selectionRange: function('s:selectionRange', [lspserver]),
+ selectionExpand: function('s:selectionExpand', [lspserver]),
+ selectionShrink: function('s:selectionShrink', [lspserver]),
foldRange: function('s:foldRange', [lspserver]),
executeCommand: function('s:executeCommand', [lspserver]),
showCapabilities: function('s:showCapabilities', [lspserver])
--- /dev/null
+vim9script
+
+# Functions related to handling LSP range selection.
+
+var util = {}
+if has('patch-8.2.4019')
+ import './util.vim' as util_import
+
+ util.GetLineByteFromPos = util_import.GetLineByteFromPos
+else
+ import {GetLineByteFromPos} from './util.vim'
+
+ util.GetLineByteFromPos = GetLineByteFromPos
+endif
+
+# Visually (character-wise) select the text in a range
+def s:selectText(bnr: number, range: dict<dict<number>>)
+ var start_col: number = util.GetLineByteFromPos(bnr, range.start) + 1
+ var end_col: number = util.GetLineByteFromPos(bnr, range.end)
+
+ :normal! v"_y
+ setcharpos("'<", [0, range.start.line + 1, start_col, 0])
+ setcharpos("'>", [0, range.end.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}
+
+ s:selectText(bnr, sel[0].range)
+enddef
+
+# Locate the range in the LSP reply at a specified level
+def s: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 s:selectionFromLSP(range: dict<any>, startpos: list<number>, endpos: list<number>): bool
+ return startpos[1] == range.start.line + 1
+ && endpos[1] == range.end.line + 1
+ && startpos[2] == range.start.character + 1
+ && endpos[2] == range.end.character
+enddef
+
+g:Logs = []
+
+# 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()
+
+ add(g:Logs, 'SelectionModify: expand = ' .. expand->string())
+
+ 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 = s:getSelRangeAtLevel(selRange, lspserver.selection.index)
+
+ # If the current selection is present in the LSP reply, then modify the
+ # selection
+ if s: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 = s:getSelRangeAtLevel(lspserver.selection.selRange, idx)
+ lspserver.selection.index = idx
+ endif
+ endif
+
+ s:selectText(bnr, selRange.range)
+ return
+ endif
+ endif
+
+ # Start a new selection
+ lspserver.selectionRange(fname)
+enddef
+
+# vim: shiftwidth=2 softtabstop=2
Author: Yegappan Lakshmanan (yegappan AT yahoo DOT com)
For Vim version 8.2.2342 and above
-Last change: Feb 2, 2022
+Last change: Feb 4, 2022
==============================================================================
*lsp-license*
:LspCodeAction Apply the code action supplied by the LSP server to
the diagnostic in the current line.
:LspSymbolSearch Perform a workspace wide search for a symbol
-:LspSelectionRange Visually select the current symbol range
+:LspSelectionExpand Expand the current symbol range visual selection
+:LspSelectionShrink Shrink the current symbol range visual selection
:LspFold Fold the current file
:LspWorkspaceAddFolder {folder}
Add a folder to the workspace
<
Default is false
- *:LspSelectionRange*
-:LspSelectionRange Visually select the current symbol range.
+ *:LspSelectionExpand*
+:LspSelectionExpand Expand the current symbol range visual selection. It
+ is useful to create a visual map to use this command.
+ Example: >
+
+ xnoremap <silent> le <Cmd>LspSelectionExpand<CR>
+<
+ With the above map, you can press "le" in visual mode
+ successively to expand the current visual region.
+
+ *:LspSelectionShrink*
+:LspSelectionShrink Shrink the current symbol range visual selection. It
+ is useful to create a visual map to use this command.
+ Example: >
+
+ xnoremap <silent> ls <Cmd>LspSelectionShrink<CR>
+<
+ With the above map, you can press "ls" in visual mode
+ successively to shrink the current visual region.
*:LspFold*
:LspFold Fold the current file.
lspf.codeAction = lsp.CodeAction
lspf.symbolSearch = lsp.SymbolSearch
lspf.hover = lsp.Hover
- lspf.selectionRange = lsp.SelectionRange
+ lspf.selectionExpand = lsp.SelectionExpand
+ lspf.selectionShrink = lsp.SelectionShrink
lspf.foldDocument = lsp.FoldDocument
lspf.listWorkspaceFolders = lsp.ListWorkspaceFolders
lspf.addWorkspaceFolder = lsp.AddWorkspaceFolder
lspf.codeAction = lsp_import.CodeAction
lspf.symbolSearch = lsp_import.SymbolSearch
lspf.hover = lsp_import.Hover
- lspf.selectionRange = lsp_import.SelectionRange
+ lspf.selectionExpand = lsp_import.SelectionExpand
+ lspf.selectionShrink = lsp_import.SelectionShrink
lspf.foldDocument = lsp_import.FoldDocument
lspf.listWorkspaceFolders = lsp_import.ListWorkspaceFolders
lspf.addWorkspaceFolder = lsp_import.AddWorkspaceFolder
CodeAction,
SymbolSearch,
Hover,
- SelectionRange,
+ SelectionExpand,
+ SelectionShrink,
FoldDocument,
ListWorkspaceFolders,
AddWorkspaceFolder,
lspf.codeAction = CodeAction
lspf.symbolSearch = SymbolSearch
lspf.hover = Hover
- lspf.selectionRange = SelectionRange
+ lspf.selectionExpand = SelectionExpand
+ lspf.selectionShrink = SelectionShrink
lspf.foldDocument = FoldDocument
lspf.listWorkspaceFolders = ListWorkspaceFolders
lspf.addWorkspaceFolder = AddWorkspaceFolder
var TcodeAction = s:lspf.codeAction
var TsymbolSearch = s:lspf.symbolSearch
var Thover = s:lspf.hover
-var TselectionRange = s:lspf.selectionRange
+var TselectionExpand = s:lspf.selectionExpand
+var TselectionShrink = s:lspf.selectionShrink
var TfoldDocument = s:lspf.foldDocument
var TlistWorkspaceFolders = s:lspf.listWorkspaceFolders
var TaddWorkspaceFolder = s:lspf.addWorkspaceFolder
command! -nargs=0 -bar LspCodeAction call TcodeAction()
command! -nargs=? -bar LspSymbolSearch call TsymbolSearch(<q-args>)
command! -nargs=0 -bar LspHover call Thover()
-command! -nargs=0 -bar LspSelectionRange call TselectionRange()
+command! -nargs=0 -bar LspSelectionExpand call TselectionExpand()
+command! -nargs=0 -bar LspSelectionShrink call TselectionShrink()
command! -nargs=0 -bar LspFold call TfoldDocument()
command! -nargs=0 -bar LspWorkspaceListFolders call TlistWorkspaceFolders()
command! -nargs=1 -bar -complete=dir LspWorkspaceAddFolder call TaddWorkspaceFolder(<q-args>)
:%bw!
enddef
-# Test for :LspSelectionRange
-def Test_LspSelectionRange()
+# Test for :LspSelectionExpand and :LspSelectionShrink
+def Test_LspSelection()
silent! edit Xtest.c
sleep 500m
var lines: list<string> =<< trim END
END
setline(1, lines)
sleep 1
- # start a block-wise visual mode, LspSelectionRange should change this to
+ # start a block-wise visual mode, LspSelectionExpand should change this to
# a characterwise visual mode.
exe "normal! 1G\<C-V>G\"_y"
cursor(2, 1)
redraw!
- :LspSelectionRange
- sleep 1
+ :LspSelectionExpand
redraw!
normal! y
assert_equal('v', visualmode())
assert_equal([2, 8], [line("'<"), line("'>")])
- # start a linewise visual mode, LspSelectionRange should change this to
+ # start a linewise visual mode, LspSelectionExpand should change this to
# a characterwise visual mode.
exe "normal! 3GViB\"_y"
cursor(4, 29)
redraw!
- :LspSelectionRange
- sleep 1
+ :LspSelectionExpand
redraw!
normal! y
assert_equal('v', visualmode())
assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
+
+ # Expand the visual selection
+ xnoremap <silent> le <Cmd>LspSelectionExpand<CR>
+ xnoremap <silent> ls <Cmd>LspSelectionShrink<CR>
+ cursor(5, 8)
+ normal vley
+ assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vleley
+ assert_equal([5, 8, 5, 14], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vleleley
+ assert_equal([4, 30, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vleleleley
+ assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vleleleleley
+ assert_equal([2, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vleleleleleley
+ assert_equal([1, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vleleleleleleley
+ assert_equal([1, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
+
+ # Shrink the visual selection
+ cursor(5, 8)
+ normal vlsy
+ assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vlelsy
+ assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vlelelsy
+ assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vlelelelsy
+ assert_equal([5, 8, 5, 14], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vlelelelelsy
+ assert_equal([4, 30, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vlelelelelelsy
+ assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
+ cursor(5, 8)
+ normal vlelelelelelelsy
+ assert_equal([2, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
+
+ xunmap le
+ xunmap ls
bw!
# empty file
- assert_equal('', execute('LspSelectionRange'))
+ assert_equal('', execute('LspSelectionExpand'))
# file without an LSP server
edit a.b
assert_equal(['Error: LSP server for "a.b" is not found'],
- execute('LspSelectionRange')->split("\n"))
+ execute('LspSelectionExpand')->split("\n"))
:%bw!
enddef