Enhance neovim's visual line mode with fully highlighted lines
neovim-plugin
at master 120 lines 3.4 kB view raw
1local M = {} 2local group_name = 'full_line_visual_mode' 3 4local a = vim.api 5M.autocmd_group = a.nvim_create_augroup(group_name, { clear = true }) 6M.nsid = a.nvim_create_namespace(group_name) 7 8function M.is_autocmd_setup() 9 return not vim.tbl_isempty(a.nvim_get_autocmds { group = M.autocmd_group }) 10end 11 12function M.setup_autocmd() 13 a.nvim_create_autocmd({ 'CursorMoved', 'ModeChanged' }, { 14 group = M.autocmd_group, 15 callback = vim.schedule_wrap(M.handle_autocmd), 16 }) 17end 18 19function M.remove_autocmd() 20 a.nvim_clear_autocmds { group = M.autocmd_group } 21end 22 23function M.draw_lines_in_range(range_start, range_end) 24 for line = range_start, range_end do 25 a.nvim_buf_set_extmark(0, M.nsid, line - 1, 0, { line_hl_group = 'Visual' }) 26 end 27end 28 29function M.clear_lines() 30 a.nvim_buf_clear_namespace(0, M.nsid, 0, -1) 31end 32 33function M.clear_lines_in_range(range_start, range_end) 34 a.nvim_buf_clear_namespace(0, M.nsid, range_start, range_end) 35end 36 37function M.cleanup() 38 M.remove_autocmd() 39 M.clear_lines() 40end 41 42-- we need to store the last positions of the start and end of the visual 43-- selection in order to partially update the selection. Fully clearing and 44-- redrawing on every update can cause flickering. 45-- 46-- {start,end}_move_delta is just for convenience 47local selection_state = nil 48 49function M.update_visual_line_state() 50 local start, _end = M.get_selection_range() 51 52 if selection_state == nil then 53 selection_state = { old_start = start, old_end = _end } 54 else 55 selection_state = { old_start = selection_state.start, old_end = selection_state._end } 56 end 57 58 selection_state.start = start 59 selection_state._end = _end 60 61 selection_state.start_move_delta = selection_state.start - selection_state.old_start 62 selection_state.end_move_delta = selection_state._end - selection_state.old_end 63 64 return selection_state 65end 66 67function M.get_selection_range() 68 local start_line, end_line = vim.fn.line 'v', vim.fn.line '.' 69 70 -- ensure the start line is always less than or equal to the end line 71 if start_line > end_line then 72 start_line, end_line = end_line, start_line 73 end 74 75 return start_line, end_line 76end 77 78function M.handle_autocmd(opts) 79 if a.nvim_get_mode().mode ~= 'V' then 80 M.clear_lines() 81 return 82 end 83 84 local state = M.update_visual_line_state() 85 86 if opts.event == 'ModeChanged' then 87 M.clear_lines() 88 M.draw_lines_in_range(state.start, state._end) 89 return 90 end 91 92 -- nothing changed 93 if state.start_move_delta == 0 and state.end_move_delta == 0 then 94 return 95 end 96 97 -- add/remove selection lines from the start/end depending on which moved 98 -- if both the start and end have moved just clear and redraw everything 99 -- 100 -- `s` = old start, `S` = new start, `e` = old end, `E` = new end 101 -- `_` = current vis line, `-` = remove vis line, `+` = add vis line 102 if state.start_move_delta ~= 0 and state.end_move_delta ~= 0 then 103 M.clear_lines() 104 M.draw_lines_in_range(state.start, state._end) 105 elseif state.start_move_delta < 0 then 106 -- ...S<+++s____E.. 107 M.draw_lines_in_range(state.start, state.old_start - 1) 108 elseif state.start_move_delta > 0 then 109 -- ...s--->S____E.. 110 M.clear_lines_in_range(state.old_start - 1, state.start - 1) 111 elseif state.end_move_delta > 0 then 112 -- ...S____e+++>E.. 113 M.draw_lines_in_range(state.old_end + 1, state._end) 114 elseif state.end_move_delta < 0 then 115 -- ...S____E<---e.. 116 M.clear_lines_in_range(state._end, state.old_end) 117 end 118end 119 120return M