From c5e41f20a1c91b7896637eb008c6cf2cb94e1c6b Mon Sep 17 00:00:00 2001
From: Yegappan Lakshmanan <yegappan@yahoo.com>
Date: Sun, 17 Jan 2021 13:38:35 -0800
Subject: [PATCH] Use the new Vim function to get/set cursor based on
 characters instead of bytes

---
 README.md              |  2 +-
 autoload/handlers.vim  | 58 +++++++++++++++---------------------------
 autoload/lsp.vim       | 36 +++++++++++++++-----------
 autoload/lspserver.vim | 21 ++++++++++-----
 autoload/util.vim      | 26 +++++++++++++++++++
 doc/lsp.txt            |  6 ++---
 plugin/lsp.vim         |  4 +--
 7 files changed, 88 insertions(+), 65 deletions(-)

diff --git a/README.md b/README.md
index 75c98a9..10f6619 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
 
-Language Server Protocol (LSP) plugin for Vim9. You need Vim version 8.2.2241 or above to use this plugin.
+Language Server Protocol (LSP) plugin for Vim9. You need Vim version 8.2.2342 or above to use this plugin.
 
 ## Installation
 
diff --git a/autoload/handlers.vim b/autoload/handlers.vim
index 53f265e..74b0045 100644
--- a/autoload/handlers.vim
+++ b/autoload/handlers.vim
@@ -4,7 +4,11 @@ vim9script
 # Refer to https://microsoft.github.io/language-server-protocol/specification
 # for the Language Server Protocol (LSP) specificaiton.
 
-import {WarnMsg, ErrMsg, TraceLog, LspUriToFile} from './util.vim'
+import {WarnMsg,
+	ErrMsg,
+	TraceLog,
+	LspUriToFile,
+	GetLineByteFromPos} from './util.vim'
 import {LspDiagsUpdated} from './buf.vim'
 
 # process the 'initialize' method reply from the LSP server
@@ -57,7 +61,7 @@ def s:processDefDeclReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>
 
   var result: dict<any> = reply.result[0]
   var file = LspUriToFile(result.uri)
-  var wid = bufwinid(file)
+  var wid = file->bufwinid()
   if wid != -1
     win_gotoid(wid)
   else
@@ -65,7 +69,8 @@ def s:processDefDeclReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>
   endif
   # Set the previous cursor location mark
   setpos("'`", getcurpos())
-  cursor(result.range.start.line + 1, result.range.start.character + 1)
+  setcursorcharpos(result.range.start.line + 1,
+			result.range.start.character + 1)
   redraw!
 enddef
 
@@ -254,7 +259,7 @@ def s:processReferencesReply(lspserver: dict<any>, req: dict<any>, reply: dict<a
 						->trim("\t ", 1)
     qflist->add({filename: fname,
 			lnum: loc.range.start.line + 1,
-			col: loc.range.start.character + 1,
+			col: GetLineByteFromPos(bnr, loc.range.start) + 1,
 			text: text})
   endfor
   setqflist([], ' ', {title: 'Language Server', items: qflist})
@@ -286,9 +291,10 @@ def s:processDocHighlightReply(lspserver: dict<any>, req: dict<any>, reply: dict
       # textual reference
       propName = 'LspTextRef'
     endif
-    prop_add(docHL.range.start.line + 1, docHL.range.start.character + 1,
+    prop_add(docHL.range.start.line + 1,
+		GetLineByteFromPos(bnr, docHL.range.start) + 1,
 		{end_lnum: docHL.range.end.line + 1,
-		  end_col: docHL.range.end.character + 1,
+		  end_col: GetLineByteFromPos(bnr, docHL.range.end) + 1,
 		  bufnr: bnr,
 		  type: propName})
   endfor
@@ -399,31 +405,6 @@ def s:processDocSymbolReply(lspserver: dict<any>, req: dict<any>, reply: dict<an
   lsp#updateOutlineWindow(fname, symbolTypeTable, symbolLineTable)
 enddef
 
-# Returns the byte number of the specified line/col position.  Returns a
-# zero-indexed column.  'pos' is LSP "interface position".
-def s:get_line_byte_from_position(bnr: number, pos: dict<number>): number
-  # LSP's line and characters are 0-indexed
-  # Vim's line and columns are 1-indexed
-  var col: number = pos.character
-  # When on the first character, we can ignore the difference between byte and
-  # character
-  if col > 0
-    if !bnr->bufloaded()
-      bnr->bufload()
-    endif
-
-    var ltext: list<string> = bnr->getbufline(pos.line + 1)
-    if !ltext->empty()
-      var bidx = ltext[0]->byteidx(col)
-      if bidx != -1
-	return bidx
-      endif
-    endif
-  endif
-
-  return col
-enddef
-
 # sort the list of edit operations in the descending order of line and column
 # numbers.
 # 'a': {'A': [lnum, col], 'B': [lnum, col]}
@@ -538,9 +519,9 @@ def s:applyTextEdits(bnr: number, text_edits: list<dict<any>>): void
   for e in text_edits
     # Adjust the start and end columns for multibyte characters
     start_row = e.range.start.line
-    start_col = s:get_line_byte_from_position(bnr, e.range.start)
+    start_col = GetLineByteFromPos(bnr, e.range.start)
     end_row = e.range.end.line
-    end_col = s:get_line_byte_from_position(bnr, e.range.end)
+    end_col = GetLineByteFromPos(bnr, e.range.end)
     start_line = [e.range.start.line, start_line]->min()
     finish_line = [e.range.end.line, finish_line]->max()
 
@@ -638,6 +619,7 @@ def s:applyWorkspaceEdit(workspaceEdit: dict<any>)
     # interface TextEdit
     s:applyTextEdits(bnr, changes)
   endfor
+  # Restore the cursor to the location before the edit
   save_cursor->setpos('.')
 enddef
 
@@ -737,9 +719,12 @@ def s:processSelectionRangeReply(lspserver: dict<any>, req: dict<any>, reply: di
   endif
 
   var r: dict<dict<number>> = reply.result[0].range
+  var bnr: number = bufnr()
+  var start_col: number = GetLineByteFromPos(bnr, r.start) + 1
+  var end_col: number = GetLineByteFromPos(bnr, r.end)
 
-  setpos("'<", [0, r.start.line + 1, r.start.character + 1, 0])
-  setpos("'>", [0, r.end.line + 1, r.end.character, 0])
+  setcharpos("'<", [0, r.start.line + 1, start_col, 0])
+  setcharpos("'>", [0, r.end.line + 1, end_col, 0])
   :normal gv
 enddef
 
@@ -834,8 +819,7 @@ def s:processWorkspaceSymbolReply(lspserver: dict<any>, req: dict<any>, reply: d
 
     symbols->add({name: symName,
 			file: fileName,
-			lnum: r.start.line + 1,
-			col: r.start.character + 1})
+			pos: r.start})
   endfor
   symbols->setwinvar(lspserver.workspaceSymbolPopup, 'LspSymbolTable')
   lspserver.workspaceSymbolPopup->popup_settext(
diff --git a/autoload/lsp.vim b/autoload/lsp.vim
index 8408ed5..f936a6f 100644
--- a/autoload/lsp.vim
+++ b/autoload/lsp.vim
@@ -3,10 +3,13 @@ vim9script
 # Vim9 LSP client
 
 import NewLspServer from './lspserver.vim'
-import {WarnMsg, ErrMsg, lsp_server_trace} from './util.vim'
+import {WarnMsg,
+	ErrMsg,
+	lsp_server_trace,
+	GetLineByteFromPos} from './util.vim'
 
-# Needs Vim 8.2.2082 and higher
-if v:version < 802 || !has('patch-8.2.2082')
+# Needs Vim 8.2.2342 and higher
+if v:version < 802 || !has('patch-8.2.2342')
   finish
 endif
 
@@ -412,7 +415,7 @@ def lsp#showDiagnostics(): void
     text = diag.message->substitute("\n\\+", "\n", 'g')
     qflist->add({'filename': fname,
 		    'lnum': diag.range.start.line + 1,
-		    'col': diag.range.start.character + 1,
+		    'col': GetLineByteFromPos(bnr, diag.range.start) + 1,
 		    'text': text,
 		    'type': s:lspDiagSevToQfType(diag.severity)})
   endfor
@@ -494,7 +497,7 @@ def lsp#jumpToDiag(which: string): void
   for lnum in (which == 'next') ? sortedDiags : reverse(sortedDiags)
     if (which == 'next' && lnum > curlnum)
 	  || (which == 'prev' && lnum < curlnum)
-      call cursor(lnum, 1)
+      cursor(lnum, 1)
       return
     endif
   endfor
@@ -527,7 +530,7 @@ def lsp#completeFunc(findstart: number, base: string): any
 
     # locate the start of the word
     var line = getline('.')
-    var start = col('.') - 1
+    var start = charcol('.') - 1
     while start > 0 && line[start - 1] =~ '\k'
       start -= 1
     endwhile
@@ -678,11 +681,12 @@ enddef
 
 var skipOutlineRefresh: bool = false
 
-def s:addSymbolText(symbolTypeTable: dict<list<dict<any>>>,
-				pfx: string,
-				text: list<string>,
-				lnumMap: list<dict<any>>,
-				children: bool)
+def s:addSymbolText(bnr: number,
+			symbolTypeTable: dict<list<dict<any>>>,
+			pfx: string,
+			text: list<string>,
+			lnumMap: list<dict<any>>,
+			children: bool)
   var prefix: string = pfx .. '  '
   for [symType, symbols] in items(symbolTypeTable)
     if !children
@@ -701,11 +705,12 @@ def s:addSymbolText(symbolTypeTable: dict<list<dict<any>>>,
     for s in symbols
       text->add(prefix .. s.name)
       # remember the line number for the symbol
+      var start_col: number = GetLineByteFromPos(bnr, s.range.start) + 1
       lnumMap->add({name: s.name, lnum: s.range.start.line + 1,
-      col: s.range.start.character + 1})
+			col: start_col})
       s.outlineLine = lnumMap->len()
       if s->has_key('children') && !s.children->empty()
-	s:addSymbolText(s.children, prefix, text, lnumMap, true)
+	s:addSymbolText(bnr, s.children, prefix, text, lnumMap, true)
       endif
     endfor
   endfor
@@ -743,7 +748,7 @@ def lsp#updateOutlineWindow(fname: string,
   # First two lines in the buffer display comment information
   var lnumMap: list<dict<any>> = [{}, {}]
   var text: list<string> = []
-  s:addSymbolText(symbolTypeTable, '', text, lnumMap, false)
+  s:addSymbolText(fname->bufnr(), symbolTypeTable, '', text, lnumMap, false)
   append('$', text)
   w:lspSymbols = {filename: fname, lnumTable: lnumMap,
 				symbolsByLine: symbolLineTable}
@@ -1112,7 +1117,8 @@ def s:jumpToWorkspaceSymbol(popupID: number, result: number): void
     else
       winList[0]->win_gotoid()
     endif
-    cursor(symTbl[result - 1].lnum, symTbl[result - 1].col)
+    setcursorcharpos(symTbl[result - 1].pos.line + 1,
+			symTbl[result - 1].pos.character + 1)
   catch
     # ignore exceptions
   endtry
diff --git a/autoload/lspserver.vim b/autoload/lspserver.vim
index 90a0009..fc344c7 100644
--- a/autoload/lspserver.vim
+++ b/autoload/lspserver.vim
@@ -4,8 +4,16 @@ vim9script
 # Refer to https://microsoft.github.io/language-server-protocol/specification
 # for the Language Server Protocol (LSP) specificaiton.
 
-import {ProcessReply, ProcessNotif, ProcessRequest, ProcessMessages} from './handlers.vim'
-import {WarnMsg, ErrMsg, ClearTraceLogs, TraceLog, LspUriToFile, LspFileToUri} from './util.vim'
+import {ProcessReply,
+	ProcessNotif,
+	ProcessRequest,
+	ProcessMessages} from './handlers.vim'
+import {WarnMsg,
+	ErrMsg,
+	ClearTraceLogs,
+	TraceLog,
+	LspUriToFile,
+	LspFileToUri} from './util.vim'
 
 # LSP server standard output handler
 def s:output_cb(lspserver: dict<any>, chan: channel, msg: string): void
@@ -84,9 +92,9 @@ def s:initServer(lspserver: dict<any>)
     textDocument: {
       foldingRange: {lineFoldingOnly: v:true},
       completion: {
-	snippetSupport: v:true,
 	completionItem: {
 	  documentationFormat: ['plaintext', 'markdown'],
+	  snippetSupport: v:false
 	},
 	completionItemKind: {valueSet: range(1, 25)}
       },
@@ -326,8 +334,7 @@ enddef
 # line and not the byte index in the line.
 def s:getLspPosition(): dict<number>
   var lnum: number = line('.') - 1
-  #var col: number = strchars(getline('.')[: col('.') - 1]) - 1
-  var col: number = col('.') - 1
+  var col: number = charcol('.') - 1
   return {line: lnum, character: col}
 enddef
 
@@ -652,8 +659,8 @@ def s:codeAction(lspserver: dict<any>, fname_arg: string)
   var bnr: number = bufnr(fname_arg)
   req.params->extend({textDocument: {uri: LspFileToUri(fname)}})
   var r: dict<dict<number>> = {
-		  start: {line: line('.') - 1, character: col('.') - 1},
-		  end: {line: line('.') - 1, character: col('.') - 1}}
+		  start: {line: line('.') - 1, character: charcol('.') - 1},
+		  end: {line: line('.') - 1, character: charcol('.') - 1}}
   req.params->extend({range: r})
   var diag: list<dict<any>> = []
   var lnum = line('.')
diff --git a/autoload/util.vim b/autoload/util.vim
index 27c54ea..0b2bef2 100644
--- a/autoload/util.vim
+++ b/autoload/util.vim
@@ -89,4 +89,30 @@ export def LspFileToUri(fname: string): string
   return uri
 enddef
 
+# Returns the byte number of the specified LSP position in buffer 'bnr'.
+# LSP's line and characters are 0-indexed.
+# Vim's line and columns are 1-indexed.
+# Returns a zero-indexed column.
+export def GetLineByteFromPos(bnr: number, pos: dict<number>): number
+  var col: number = pos.character
+  # When on the first character, we can ignore the difference between byte and
+  # character
+  if col > 0
+    # Need a loaded buffer to read the line and compute the offset
+    if !bnr->bufloaded()
+      bnr->bufload()
+    endif
+
+    var ltext: list<string> = bnr->getbufline(pos.line + 1)
+    if !ltext->empty()
+      var bidx = ltext[0]->byteidx(col)
+      if bidx != -1
+	return bidx
+      endif
+    endif
+  endif
+
+  return col
+enddef
+
 # vim: shiftwidth=2 softtabstop=2
diff --git a/doc/lsp.txt b/doc/lsp.txt
index 5658f3b..d280ba1 100644
--- a/doc/lsp.txt
+++ b/doc/lsp.txt
@@ -1,8 +1,8 @@
 *lsp.txt*	Language Server Protocol (LSP) Plugin for Vim9
 
 Author: Yegappan Lakshmanan  (yegappan AT yahoo DOT com)
-For Vim version 8.2.2082 and above
-Last change: Jan 10, 2021
+For Vim version 8.2.2342 and above
+Last change: Jan 17, 2021
 
 ==============================================================================
 						*lsp-license*
@@ -35,7 +35,7 @@ Refer to the following pages for more information about LSP:
     https://microsoft.github.io/language-server-protocol/
     https://langserver.org/
 
-This plugin needs Vim version 8.2.2082 and after. You will need a language
+This plugin needs Vim version 8.2.2342 and after. You will need a language
 specific server in your system to use this plugin. Refer to the above pages
 for a list of available language servers for the various programming
 languages.
diff --git a/plugin/lsp.vim b/plugin/lsp.vim
index 0797813..d1af460 100644
--- a/plugin/lsp.vim
+++ b/plugin/lsp.vim
@@ -1,7 +1,7 @@
 " LSP plugin for vim9
 
-" Needs Vim 8.2.2082 and higher
-if v:version < 802 || !has('patch-8.2.2082')
+" Needs Vim 8.2.2342 and higher
+if v:version < 802 || !has('patch-8.2.2342')
   finish
 endif
 
-- 
2.51.0