It's a bit complicated, I'll try to explain it in different abstraction levels.
Level 0
our goal is simple:
- update command range highlight after entering command line
- update command range highlight while typing in command line
- clear command range highlight when leaving command line
Level 1
We will use :h CmdlineEnter , :h CmdlineChanged , and :h CmdlineLeave to implement our goals.
augroup cmdline_range_highlight
au!
autocmd CmdlineChanged : call s:update_cmdrange_highlight()
autocmd CmdlineEnter : call s:update_cmdrange_highlight()
autocmd CmdlineLeave : call s:clear_cmdrange_highlight()
augroup end
Level 2
Let's implement functions mentioned in Level 1
, their name should already tell you what they do.
" last command range and hightlight match id returned from matchadd()
let s:cmdrange = []
let s:cmdrange_hid = -1
function s:update_cmdrange_highlight() abort
let cmdline = getcmdline()
" skip v_:
if cmdline =~# '\V\^''<,''>'
return
endif
" skip if range not changed
let cmdrange = s:get_cmdrange(cmdline)
if s:cmdrange == cmdrange
return
endif
" update highlight area
call s:clear_cmdrange_highlight()
let s:cmdrange_hid = matchadd('VISUAL',
\ printf('\v%%>%dl.*%%<%dl', cmdrange[0]-1, cmdrange[1]+1))
let s:cmdrange = cmdrange
redraw
endfunction
function s:clear_cmdrange_highlight() abort
if s:cmdrange_hid != -1
call matchdelete(s:cmdrange_hid)
endif
let s:cmdrange = []
let s:cmdrange_hid = -1
endfunction
Level 3
We need to implement s:get_cmdrange
to get current cmd range
function s:get_cmdrange()
????????????
endfunction
There has no builtin function to get cmdrange without executing it(correct me if I'm wrong), the only way to get a cmdrange is executing a cmd. We can't execute current command, we must construct another labor command with current cmdrange text:
function s:get_cmdrange(cmdline) abort
let range_text = matchstr(a:cmdline, s:cmdrange_pat)
if empty(range_text)
return []
endif
try
let cpos = getcurpos()
" silent is used to reverse reversed command range
exec printf('silent %s call Tech_get_cmdrange()', range_text)
return [s:tech_cmdrange_line1, s:tech_cmdrange_line2]
finally
call setpos('.', cpos)
endtry
endfunction
function Tech_get_cmdrange() range
let s:tech_cmdrange_line1 = a:firstline
let s:tech_cmdrange_line2 = a:lastline
endfunction
Our labor command here is silent original_command_range_text call Tech_get_cmdrange()
.
We use :h :func-range to get cmdline range from vim
.
Level 4
The last step is to init s:cmdrange_pat
, it's used in Level 3 to extract cmdline range from cmdline.
A :h cmdline-ranges is one :h :range or two :range
joined by ,
or ;
A :range
is some basic pattern followed by multiple optional [+-][number]
.
function s:build_cmdrange_pattern()
" a range is various pattern followed by multiple optional [+-][number]
let range_atoms = [
\ '\d+',
\ '[.$%]',
\ "'[0-9A-Z]",
\ '\/.{-}\/',
\ '\?.{-}\?',
\ '\\\?',
\ '\\\&',
\ '\\\&',
\ ''
\ ]
let range_basic = printf('%%(%s)', join(range_atoms, '|'))
let range_optional = '%(\s*%([+-]\d*)*)'
let range_pat = printf('\v%%(%s\s*%s*)', range_basic, range_optional)
" cmdrange is two ranges joined by, or ;
let cmdrange_pat = printf('\v\C^%s([,;]%s)?', range_pat, range_pat)
return cmdrange_pat
endfunction
let s:cmdrange_pat = s:build_cmdrange_pattern()
cnoremap
though