: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