]> Sergey Matveev's repositories - vim-lsp.git/commitdiff
Move the text edit and code action functions to a separate file
authorYegappan Lakshmanan <yegappan@yahoo.com>
Fri, 14 Jan 2022 15:22:27 +0000 (07:22 -0800)
committerYegappan Lakshmanan <yegappan@yahoo.com>
Fri, 14 Jan 2022 15:22:27 +0000 (07:22 -0800)
autoload/codeaction.vim [new file with mode: 0644]
autoload/handlers.vim
autoload/lspoptions.vim
autoload/lspserver.vim
autoload/outline.vim
autoload/symbolsearch.vim
autoload/textedit.vim [new file with mode: 0644]
doc/lsp.txt
test/test_lsp.vim

diff --git a/autoload/codeaction.vim b/autoload/codeaction.vim
new file mode 100644 (file)
index 0000000..28fec07
--- /dev/null
@@ -0,0 +1,58 @@
+vim9script
+
+var util = {}
+var textedit = {}
+
+if has('patch-8.2.4019')
+  import './util.vim' as util_import
+  import './textedit.vim' as textedit_import
+
+  util.WarnMsg = util_import.WarnMsg
+  textedit.ApplyWorkspaceEdit = textedit_import.ApplyWorkspaceEdit
+else
+  import WarnMsg from './util.vim'
+  import ApplyWorkspaceEdit from './textedit.vim'
+
+  util.WarnMsg = WarnMsg
+  textedit.ApplyWorkspaceEdit = ApplyWorkspaceEdit
+endif
+
+export def ApplyCodeAction(lspserver: dict<any>, actions: list<dict<any>>): void
+  if actions->empty()
+    # no action can be performed
+    util.WarnMsg('No code action is available')
+    return
+  endif
+
+  var prompt: list<string> = ['Code Actions:']
+  var act: dict<any>
+  for i in range(actions->len())
+    act = actions[i]
+    var t: string = act.title->substitute('\r\n', '\\r\\n', 'g')
+    t = t->substitute('\n', '\\n', 'g')
+    prompt->add(printf("%d. %s", i + 1, t))
+  endfor
+  var choice = inputlist(prompt)
+  if choice < 1 || choice > prompt->len()
+    return
+  endif
+
+  var selAction = actions[choice - 1]
+
+  # 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.
+  if selAction->has_key('edit') || selAction->has_key('command')
+    if selAction->has_key('edit')
+      # apply edit first
+      textedit.ApplyWorkspaceEdit(selAction.edit)
+    endif
+    if selAction->has_key('command')
+      lspserver.executeCommand(selAction)
+    endif
+  else
+    lspserver.executeCommand(selAction)
+  endif
+enddef
+
+# vim: shiftwidth=2 softtabstop=2
index 6b32d33927691ca9ca35900b237125aa08d20e3b..8f16e9f4e549fd96d759c0c21c892ddf9a442c1d 100644 (file)
@@ -8,12 +8,16 @@ var opt = {}
 var util = {}
 var diag = {}
 var outline = {}
+var textedit = {}
+var codeaction = {}
 
 if has('patch-8.2.4019')
   import './lspoptions.vim' as opt_import
   import './util.vim' as util_import
   import './diag.vim' as diag_import
   import './outline.vim' as outline_import
+  import './textedit.vim' as textedit_import
+  import './codeaction.vim' as codeaction_import
 
   opt.lspOptions = opt_import.lspOptions
   util.WarnMsg = util_import.WarnMsg
@@ -23,6 +27,9 @@ if has('patch-8.2.4019')
   util.GetLineByteFromPos = util_import.GetLineByteFromPos
   diag.DiagNotification = diag_import.DiagNotification
   outline.UpdateOutlineWindow = outline_import.UpdateOutlineWindow
+  textedit.ApplyTextEdits = textedit_import.ApplyTextEdits
+  textedit.ApplyWorkspaceEdit = textedit_import.ApplyWorkspaceEdit
+  codeaction.ApplyCodeAction = codeaction_import.ApplyCodeAction
 else
   import lspOptions from './lspoptions.vim'
   import {WarnMsg,
@@ -32,6 +39,8 @@ else
        GetLineByteFromPos} from './util.vim'
   import DiagNotification from './diag.vim'
   import UpdateOutlineWindow from './outline.vim'
+  import {ApplyTextEdits, ApplyWorkspaceEdit} from './textedit.vim'
+  import ApplyCodeAction from './codeaction.vim'
 
   opt.lspOptions = lspOptions
   util.WarnMsg = WarnMsg
@@ -41,6 +50,9 @@ else
   util.GetLineByteFromPos = GetLineByteFromPos
   diag.DiagNotification = DiagNotification
   outline.UpdateOutlineWindow = UpdateOutlineWindow
+  textedit.ApplyTextEdits = ApplyTextEdits
+  textedit.ApplyWorkspaceEdit = ApplyWorkspaceEdit
+  codeaction.ApplyCodeAction = ApplyCodeAction
 endif
 
 # process the 'initialize' method reply from the LSP server
@@ -550,228 +562,6 @@ def s:processDocSymbolReply(lspserver: dict<any>, req: dict<any>, reply: dict<an
   outline.UpdateOutlineWindow(fname, symbolTypeTable, symbolLineTable)
 enddef
 
-# 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 s: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
-
-  return 0
-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 s: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 range(new_lines_len)
-    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
-def s: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
-  if !bnr->bufloaded()
-    bnr->bufload()
-  endif
-  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.
-  for e in text_edits
-    # Adjust the start and end columns for multibyte characters
-    start_row = e.range.start.line
-    start_col = util.GetLineByteFromPos(bnr, e.range.start)
-    end_row = e.range.end.line
-    end_col = util.GetLineByteFromPos(bnr, e.range.end)
-    start_line = [e.range.start.line, start_line]->min()
-    finish_line = [e.range.end.line, finish_line]->max()
-
-    updated_edits->add({A: [start_row, start_col],
-                       B: [end_row, end_col],
-                       lines: e.newText->split("\n", true)})
-  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('s: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 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 = s: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 set_eol && lines[-1]->len() == 0
-    lines->remove(-1)
-  endif
-
-  #echomsg 'applyTextEdits: start_line = ' .. start_line .. ', finish_line = ' .. finish_line
-  #echomsg 'lines = ' .. string(lines)
-
-  # Delete all the lines that need to be modified
-  bnr->deletebufline(start_line + 1, finish_line + 1)
-
-  # 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)[0] == ''
-    dellastline = true
-  endif
-
-  # Append the updated lines
-  appendbufline(bnr, start_line, lines)
-
-  if dellastline
-    bnr->deletebufline(bnr->getbufinfo()[0].linecount)
-  endif
-enddef
-
-# interface TextDocumentEdit
-def s:applyTextDocumentEdit(textDocEdit: dict<any>)
-  var bnr: number = bufnr(util.LspUriToFile(textDocEdit.textDocument.uri))
-  if bnr == -1
-    util.ErrMsg('Error: Text Document edit, buffer ' .. textDocEdit.textDocument.uri .. ' is not found')
-    return
-  endif
-  s:applyTextEdits(bnr, textDocEdit.edits)
-enddef
-
-# interface WorkspaceEdit
-def s:applyWorkspaceEdit(workspaceEdit: dict<any>)
-  if workspaceEdit->has_key('documentChanges')
-    for change in workspaceEdit.documentChanges
-      if change->has_key('kind')
-       util.ErrMsg('Error: Unsupported change in workspace edit [' .. change.kind .. ']')
-      else
-       s:applyTextDocumentEdit(change)
-      endif
-    endfor
-    return
-  endif
-
-  if !workspaceEdit->has_key('changes')
-    return
-  endif
-
-  var save_cursor: list<number> = getcurpos()
-  for [uri, changes] in workspaceEdit.changes->items()
-    var fname: string = util.LspUriToFile(uri)
-    var bnr: number = fname->bufnr()
-    if bnr == -1
-      # file is already removed
-      continue
-    endif
-
-    # interface TextEdit
-    s:applyTextEdits(bnr, changes)
-  endfor
-  # Restore the cursor to the location before the edit
-  save_cursor->setpos('.')
-enddef
-
 # process the 'textDocument/formatting' reply from the LSP server
 # Result: TextEdit[] | null
 def s:processFormatReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>)
@@ -792,7 +582,7 @@ def s:processFormatReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>)
   # interface TextEdit
   # Apply each of the text edit operations
   var save_cursor: list<number> = getcurpos()
-  s:applyTextEdits(bnr, reply.result)
+  textedit.ApplyTextEdits(bnr, reply.result)
   save_cursor->setpos('.')
 enddef
 
@@ -806,58 +596,13 @@ def s:processRenameReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>)
   endif
 
   # result: WorkspaceEdit
-  s:applyWorkspaceEdit(reply.result)
-enddef
-
-# Request the LSP server to execute a command
-# Request: workspace/executeCommand
-# Params: ExecuteCommandParams
-def s:executeCommand(lspserver: dict<any>, cmd: dict<any>)
-  var req = lspserver.createRequest('workspace/executeCommand')
-  req.params->extend(cmd)
-  lspserver.sendMessage(req)
+  textedit.ApplyWorkspaceEdit(reply.result)
 enddef
 
 # process the 'textDocument/codeAction' reply from the LSP server
 # Result: (Command | CodeAction)[] | null
 def s:processCodeActionReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>)
-  if reply.result->empty()
-    # no action can be performed
-    util.WarnMsg('No code action is available')
-    return
-  endif
-
-  var actions: list<dict<any>> = reply.result
-
-  var prompt: list<string> = ['Code Actions:']
-  var act: dict<any>
-  for i in range(actions->len())
-    act = actions[i]
-    var t: string = act.title->substitute('\r\n', '\\r\\n', 'g')
-    t = t->substitute('\n', '\\n', 'g')
-    prompt->add(printf("%d. %s", i + 1, t))
-  endfor
-  var choice = inputlist(prompt)
-  if choice < 1 || choice > prompt->len()
-    return
-  endif
-
-  var selAction = actions[choice - 1]
-
-  # 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.
-  if selAction->has_key('edit') || selAction->has_key('command')
-    if selAction->has_key('edit')
-      # apply edit first
-      s:applyWorkspaceEdit(selAction.edit)
-    endif
-    if selAction->has_key('command')
-      s:executeCommand(lspserver, selAction)
-    endif
-  else
-    s:executeCommand(lspserver, selAction)
-  endif
+  codeaction.ApplyCodeAction(lspserver, reply.result)
 enddef
 
 # Reply: 'textDocument/selectionRange'
@@ -1109,7 +854,7 @@ def s:processApplyEditReq(lspserver: dict<any>, request: dict<any>)
   if workspaceEditParams->has_key('label')
     :echomsg "Workspace edit" .. workspaceEditParams.label
   endif
-  s:applyWorkspaceEdit(workspaceEditParams.edit)
+  textedit.ApplyWorkspaceEdit(workspaceEditParams.edit)
   # TODO: Need to return the proper result of the edit operation
   lspserver.sendResponse(request, {applied: true}, {})
 enddef
index 82984366a63167ddc4b540c7860ae274d5aca141..33e73bd7a6ea307c731c14cd2ad0692ee7eeb5ed 100644 (file)
@@ -25,3 +25,5 @@ export def LspOptionsSet(opts: dict<any>)
     lspOptions[key] = opts[key]
   endfor
 enddef
+
+# vim: shiftwidth=2 softtabstop=2
index cef6987638e96f6b152deb1d56b2580c33cd81b8..a1dff8dbeea77103e55aeba46ee65a26dd162b2f 100644 (file)
@@ -818,6 +818,15 @@ def s:foldRange(lspserver: dict<any>, fname: string)
   lspserver.sendMessage(req)
 enddef
 
+# Request the LSP server to execute a command
+# Request: workspace/executeCommand
+# Params: ExecuteCommandParams
+def s:executeCommand(lspserver: dict<any>, cmd: dict<any>)
+  var req = lspserver.createRequest('workspace/executeCommand')
+  req.params->extend(cmd)
+  lspserver.sendMessage(req)
+enddef
+
 export def NewLspServer(path: string, args: list<string>): dict<any>
   var lspserver: dict<any> = {
     path: path,
@@ -876,7 +885,8 @@ export def NewLspServer(path: string, args: list<string>): dict<any>
     addWorkspaceFolder: function('s:addWorkspaceFolder', [lspserver]),
     removeWorkspaceFolder: function('s:removeWorkspaceFolder', [lspserver]),
     selectionRange: function('s:selectionRange', [lspserver]),
-    foldRange: function('s:foldRange', [lspserver])
+    foldRange: function('s:foldRange', [lspserver]),
+    executeCommand: function('s:executeCommand', [lspserver])
   })
 
   return lspserver
index a8cf9183124caf57a79e4a12141188c65f9d10c2..53021ee96994fd839a43116abd7949fb7b108086 100644 (file)
@@ -276,3 +276,4 @@ export def OpenOutlineWindow()
   prevWinID->win_gotoid()
 enddef
 
+# vim: shiftwidth=2 softtabstop=2
index 3d463544e81e4f2de796d52f94262db70787781a..cee7266dd85c96ee43bd7056461d0c63b61a314d 100644 (file)
@@ -142,3 +142,4 @@ export def ShowSymbolMenu(lspserver: dict<any>, query: string)
   echo 'Symbol: ' .. query
 enddef
 
+# vim: shiftwidth=2 softtabstop=2
diff --git a/autoload/textedit.vim b/autoload/textedit.vim
new file mode 100644 (file)
index 0000000..abddb4e
--- /dev/null
@@ -0,0 +1,247 @@
+vim9script
+
+var util = {}
+
+if has('patch-8.2.4019')
+  import './util.vim' as util_import
+
+  util.WarnMsg = util_import.WarnMsg
+  util.ErrMsg = util_import.ErrMsg
+  util.LspUriToFile = util_import.LspUriToFile
+  util.GetLineByteFromPos = util_import.GetLineByteFromPos
+else
+  import {WarnMsg,
+       ErrMsg,
+       TraceLog,
+       LspUriToFile,
+       GetLineByteFromPos} from './util.vim'
+
+  util.WarnMsg = WarnMsg
+  util.ErrMsg = ErrMsg
+  util.LspUriToFile = LspUriToFile
+  util.GetLineByteFromPos = GetLineByteFromPos
+endif
+
+# 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 s: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
+
+  return 0
+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 s: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 range(new_lines_len)
+    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
+  if !bnr->bufloaded()
+    bnr->bufload()
+  endif
+  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.
+  for e in text_edits
+    # Adjust the start and end columns for multibyte characters
+    start_row = e.range.start.line
+    start_col = util.GetLineByteFromPos(bnr, e.range.start)
+    end_row = e.range.end.line
+    end_col = util.GetLineByteFromPos(bnr, e.range.end)
+    start_line = [e.range.start.line, start_line]->min()
+    finish_line = [e.range.end.line, finish_line]->max()
+
+    updated_edits->add({A: [start_row, start_col],
+                       B: [end_row, end_col],
+                       lines: e.newText->split("\n", true)})
+  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('s: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 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 = s: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 set_eol && lines[-1]->len() == 0
+    lines->remove(-1)
+  endif
+
+  #echomsg 'ApplyTextEdits: start_line = ' .. start_line .. ', finish_line = ' .. finish_line
+  #echomsg 'lines = ' .. string(lines)
+
+  # Delete all the lines that need to be modified
+  bnr->deletebufline(start_line + 1, finish_line + 1)
+
+  # 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)[0] == ''
+    dellastline = true
+  endif
+
+  # Append the updated lines
+  appendbufline(bnr, start_line, lines)
+
+  if dellastline
+    bnr->deletebufline(bnr->getbufinfo()[0].linecount)
+  endif
+enddef
+
+# interface TextDocumentEdit
+def s:applyTextDocumentEdit(textDocEdit: dict<any>)
+  var bnr: number = bufnr(util.LspUriToFile(textDocEdit.textDocument.uri))
+  if bnr == -1
+    util.ErrMsg('Error: Text Document edit, buffer ' .. textDocEdit.textDocument.uri .. ' is not found')
+    return
+  endif
+  ApplyTextEdits(bnr, textDocEdit.edits)
+enddef
+
+# interface WorkspaceEdit
+export def ApplyWorkspaceEdit(workspaceEdit: dict<any>)
+  if workspaceEdit->has_key('documentChanges')
+    for change in workspaceEdit.documentChanges
+      if change->has_key('kind')
+       util.ErrMsg('Error: Unsupported change in workspace edit [' .. change.kind .. ']')
+      else
+       s:applyTextDocumentEdit(change)
+      endif
+    endfor
+    return
+  endif
+
+  if !workspaceEdit->has_key('changes')
+    return
+  endif
+
+  var save_cursor: list<number> = getcurpos()
+  for [uri, changes] in workspaceEdit.changes->items()
+    var fname: string = util.LspUriToFile(uri)
+    var bnr: number = fname->bufnr()
+    if bnr == -1
+      # file is already removed
+      continue
+    endif
+
+    # interface TextEdit
+    ApplyTextEdits(bnr, changes)
+  endfor
+  # Restore the cursor to the location before the edit
+  save_cursor->setpos('.')
+enddef
+
+# vim: shiftwidth=2 softtabstop=2
index 1422d4e2f6df8809f1b01d99d145a5af326d3b66..2976c7b3acf5e2004e4fb62454159e60b930f8db 100644 (file)
@@ -334,10 +334,13 @@ diagnostic messages, you can add the following line to your .vimrc file:
 
                                                *:LspSymbolSearch*
 :LspSymbolSearch <sym> Perform a workspace wide search for the symbol <sym>.
-                       A popup window is opened with the list of matching
-                       symbols.  You can enter a few characters to narrow
-                       down the list of matches. You can close the popup menu
-                       by pressing the escape key or by pressing CTRL-C.
+                       If <sym> is not supplied, then you will be prompted to
+                       enter the symbol name (the keyword under the cursor is
+                       used as the default).  A popup window is opened with
+                       the list of matching symbols.  You can enter a few
+                       characters to narrow down the list of matches. You can
+                       close the popup menu by pressing the escape key or by
+                       pressing CTRL-C.
 
                        In the popup menu, the following keys can be used:
 
index b71abb9b69a722347130dd483e22c25112101704..df0c8e343c53e663c0f674a0f7bbebd49bd1ce5d 100644 (file)
@@ -138,6 +138,7 @@ def Test_lsp_show_references()
   :%bw!
 enddef
 
+# Test for LSP diagnostics
 def Test_lsp_diags()
   :silent! edit Xtest.c
   var lines: list<string> =<< trim END
@@ -190,6 +191,40 @@ def Test_lsp_diags()
   :%bw!
 enddef
 
+# Test for LSP code action to apply fixes
+def Test_lsp_codeaction()
+  var lines: list<string> =<< trim END
+    void testFunc()
+    {
+       int count;
+       count == 20;
+    }
+  END
+  writefile(lines, 'Xtest.c')
+  var args: list<any> = v:argv
+  args->add('Xtest.c')
+  var buf = term_start(args, {term_finish: 'close'})
+  buf->term_wait()
+  sleep 100m
+  buf->term_sendkeys('4G')
+  buf->term_wait()
+  buf->term_sendkeys(":LspCodeAction\<CR>")
+  buf->term_wait()
+  sleep 100m
+  buf->term_sendkeys("1")
+  sleep 100m
+  buf->term_sendkeys("\<CR>")
+  buf->term_wait()
+  sleep 100m
+  buf->term_sendkeys(":wq\<CR>")
+  buf->term_wait()
+  sleep 100m
+  var l = readfile('Xtest.c')
+  assert_equal("\tcount = 20;", l[3])
+  delete('Xtest.c')
+  :%bw!
+enddef
+
 def LspRunTests()
   # Edit a dummy C file to start the LSP server
   :edit Xtest.c