116116--- - To stop LSP server from suggesting snippets, disable (set to `false`) the
117117--- following capability during LSP server start:
118118--- `textDocument.completion.completionItem.snippetSupport`.
119- --- - If snippet body doesn't contain tabstops, `lsp_completion.snippet_insert`
120- --- is not called and text is inserted as is.
119+ --- - If snippet body doesn't contain tabstop, variable, tab, or newline,
120+ --- `lsp_completion.snippet_insert` is not called and text is inserted as- is.
121121---
122122--- # Notes ~
123123---
@@ -689,6 +689,10 @@ H.keys = {
689689 ctrl_n = vim .api .nvim_replace_termcodes (' <C-g><C-g><C-n>' , true , false , true ),
690690}
691691
692+ -- Flags for whether there is support for dedicated options
693+ H .has_winborder = vim .fn .has (' nvim-0.11' ) == 1
694+ H .has_pumborder = vim .fn .exists (' +pumborder' ) == 1 -- Neovim>=0.12
695+
692696-- Caches for different actions -----------------------------------------------
693697-- Field `lsp` is a table describing state of all used LSP requests. It has the
694698-- following structure:
@@ -1243,11 +1247,14 @@ H.lsp_completion_response_items_to_complete_items = function(items)
12431247
12441248 local is_snippet_kind = item .kind == snippet_kind
12451249 local is_snippet_format = item .insertTextFormat == snippet_inserttextformat
1246- -- Treat item as snippet only if it has tabstops or variables. This is
1247- -- important to make "implicit" expand work with LSP servers that report
1248- -- even regular words as `InsertTextFormat.Snippet` (like `gopls`).
1249- local needs_snippet_insert = (is_snippet_kind or is_snippet_format )
1250- and (word :find (' [^\\ ]%${?%w' ) ~= nil or word :find (' ^%${?%w' ) ~= nil )
1250+ -- Treat item as snippet only if it has tabstop, variable, tab, or newline.
1251+ -- It is important to make "implicit" expand work with LSP servers that
1252+ -- report even regular words as `InsertTextFormat.Snippet` (like `gopls`).
1253+ -- Otherwise it will "eat" the next typed non-keyword charater.
1254+ -- Account for tabs and newline to allow `snippet_insert` to deal with
1255+ -- reindenting and tab expansion.
1256+ local has_snippet_features = (word :find (' [^\\ ]%${?%w' ) or word :find (' ^%${?%w' ) or word :find (' [\n\t ]' )) ~= nil
1257+ local needs_snippet_insert = (is_snippet_kind or is_snippet_format ) and has_snippet_features
12511258
12521259 local details = item .labelDetails or {}
12531260 -- NOTE: Using `table.concat({}, ' ')` would be cleaner but less performant
@@ -1338,7 +1345,7 @@ H.make_lsp_extra_actions = function(lsp_data)
13381345 -- snippet is inserted and its session is active.
13391346 local cur = vim .api .nvim_win_get_cursor (0 )
13401347 local extmark_opts = { end_row = cur [1 ] - 1 , end_col = cur [2 ], right_gravity = false , end_right_gravity = true }
1341- local track_id = vim .api .nvim_buf_set_extmark (0 , H .ns_id , cur [1 ] - 1 , cur [2 ], extmark_opts )
1348+ local track_extmark_id = vim .api .nvim_buf_set_extmark (0 , H .ns_id , cur [1 ] - 1 , cur [2 ], extmark_opts )
13421349
13431350 vim .schedule (function ()
13441351 -- Do nothing if user exited Insert mode
@@ -1349,8 +1356,7 @@ H.make_lsp_extra_actions = function(lsp_data)
13491356 -- created by server), but only if there is snippet (keep new characters
13501357 -- for *only* text edits).
13511358 if snippet ~= nil then
1352- local ok , new = pcall (vim .api .nvim_buf_get_extmark_by_id , 0 , H .ns_id , track_id , { details = true })
1353- if ok then vim .api .nvim_buf_set_text (0 , new [1 ], new [2 ], new [3 ].end_row , new [3 ].end_col , {}) end
1359+ H .del_extmark (track_extmark_id , true )
13541360 pcall (vim .api .nvim_win_set_cursor , 0 , cur )
13551361 end
13561362
@@ -1364,6 +1370,8 @@ H.make_lsp_extra_actions = function(lsp_data)
13641370 local prefix = string.rep (' x' , init_base .length )
13651371 pcall (vim .api .nvim_buf_set_text , 0 , from [1 ] - 1 , from [2 ], to [1 ] - 1 , to [2 ], { prefix })
13661372 to = { from [1 ], from [2 ] + init_base .length }
1373+ local prefix_extmark_opts = { end_row = to [1 ] - 1 , end_col = to [2 ] }
1374+ local prefix_extmark_id = vim .api .nvim_buf_set_extmark (0 , H .ns_id , from [1 ] - 1 , from [2 ], prefix_extmark_opts )
13671375
13681376 -- Possibly adjust tracked range to come from LSP item. Clamp to existing
13691377 -- text state because some LSP servers update `textEdit` during resolve
@@ -1385,6 +1393,9 @@ H.make_lsp_extra_actions = function(lsp_data)
13851393
13861394 -- Expand snippet: remove base and insert at cursor
13871395 pcall (vim .api .nvim_buf_set_text , 0 , from [1 ] - 1 , from [2 ], to [1 ] - 1 , to [2 ], { ' ' })
1396+ -- - Ensure to work with bad `textEdit`, like not covering cursor position
1397+ vim .api .nvim_win_set_cursor (0 , from )
1398+ H .del_extmark (prefix_extmark_id , true )
13881399 local insert = H .get_config ().lsp_completion .snippet_insert or MiniCompletion .default_snippet_insert
13891400 insert (snippet )
13901401 end )
@@ -1410,15 +1421,12 @@ H.apply_tracked_text_edits = function(client_id, text_edits, from, to)
14101421 H .apply_text_edits (client_id , text_edits )
14111422
14121423 -- Restore cursor position
1413- local cursor_data = vim .api .nvim_buf_get_extmark_by_id (0 , H .ns_id , cursor_extmark_id , {})
1414- vim .api .nvim_buf_del_extmark (0 , H .ns_id , cursor_extmark_id )
1424+ local cursor_data = H .del_extmark (cursor_extmark_id )
14151425 pcall (vim .api .nvim_win_set_cursor , 0 , { cursor_data [1 ] + 1 , cursor_data [2 ] })
14161426
14171427 -- Update in place tracked range
1418- local from_data = vim .api .nvim_buf_get_extmark_by_id (0 , H .ns_id , from_extmark_id , {})
1419- vim .api .nvim_buf_del_extmark (0 , H .ns_id , from_extmark_id )
1420- local to_data = vim .api .nvim_buf_get_extmark_by_id (0 , H .ns_id , to_extmark_id , {})
1421- vim .api .nvim_buf_del_extmark (0 , H .ns_id , to_extmark_id )
1428+ local from_data = H .del_extmark (from_extmark_id )
1429+ local to_data = H .del_extmark (to_extmark_id )
14221430 return { from_data [1 ] + 1 , from_data [2 ] }, { to_data [1 ] + 1 , to_data [2 ] }
14231431end
14241432
@@ -1525,8 +1533,9 @@ end
15251533
15261534H .info_window_options = function ()
15271535 local win_config = H .get_config ().window .info
1528- local default_border = (vim . fn . exists ( ' +winborder ' ) == 1 and vim .o .winborder ~= ' ' ) and vim .o .winborder or ' single'
1536+ local default_border = (H . has_winborder and vim .o .winborder ~= ' ' ) and vim .o .winborder or ' single'
15291537 local border = win_config .border or default_border
1538+ local pumborder = H .has_pumborder and vim .o .pumborder or ' '
15301539
15311540 -- Compute dimensions based on actually visible lines to be displayed
15321541 local lines = H .compute_visible_md_lines (vim .api .nvim_buf_get_lines (H .info .bufnr , 0 , - 1 , false ))
@@ -1535,7 +1544,8 @@ H.info_window_options = function()
15351544 -- Compute position
15361545 local event = H .info .event
15371546 local left_to_pum = event .col - 1
1538- local right_to_pum = event .col + event .width + (event .scrollbar and 1 or 0 )
1547+ local offset = (pumborder == ' ' or pumborder == ' none' ) and (event .scrollbar and 1 or 0 ) or 2
1548+ local right_to_pum = event .col + event .width + offset
15391549
15401550 local border_offset = border == ' none' and 0 or 2
15411551 local space_left = left_to_pum - border_offset
@@ -1699,7 +1709,7 @@ end
16991709
17001710H .signature_window_opts = function ()
17011711 local win_config = H .get_config ().window .signature
1702- local default_border = (vim . fn . exists ( ' +winborder ' ) == 1 and vim .o .winborder ~= ' ' ) and vim .o .winborder or ' single'
1712+ local default_border = (H . has_winborder and vim .o .winborder ~= ' ' ) and vim .o .winborder or ' single'
17031713 local border = win_config .border or default_border
17041714 local lines = vim .api .nvim_buf_get_lines (H .signature .bufnr , 0 , - 1 , false )
17051715 local height , width = H .floating_dimensions (lines , win_config .height , win_config .width )
@@ -1879,6 +1889,18 @@ H.get_lsp_edit_range = function(response_data)
18791889 end
18801890end
18811891
1892+ H .del_extmark = function (extmark_id , with_text )
1893+ local data = vim .api .nvim_buf_get_extmark_by_id (0 , H .ns_id , extmark_id , { details = true })
1894+ vim .api .nvim_buf_del_extmark (0 , H .ns_id , extmark_id )
1895+ -- Possibly remove extmark's text
1896+ if not with_text or data [1 ] == nil or data [3 ].end_row == nil then return data end
1897+ local start_row , start_col , end_row , end_col = data [1 ], data [2 ], data [3 ].end_row , data [3 ].end_col
1898+ if start_row < end_row or (start_row == end_row and start_col < end_col ) then
1899+ vim .api .nvim_buf_set_text (0 , start_row , start_col , end_row , end_col , { ' ' })
1900+ end
1901+ return data
1902+ end
1903+
18821904H .is_whitespace = function (s )
18831905 if type (s ) == ' string' then return s :find (' ^%s*$' ) end
18841906 if type (s ) == ' table' then
0 commit comments