I'm trying to add more ex commands to my vim repertoire and I was wondering if there is setting or plugin that would allow to highlight the lines addressed by the command, if a range is give e.g., if I type


I would like some highlight on the lines -5 and -3 (either the line number or the whole line or something like that), while I'm typing the command, until I actually execute it. If only the begin or end or the whole block is highlighted is not so important me.

I searched the vim help and the web for some time but did not find anything useful so far.

  • This seems like it could be pretty non-trivial if not implemented in source code. You might get by with a cnoremap though – D. Ben Knoble Nov 9 '19 at 2:07

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
  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\^''<,''>'

  " skip if range not changed
  let cmdrange = s:get_cmdrange(cmdline)
  if s:cmdrange == cmdrange

  " 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


function s:clear_cmdrange_highlight() abort
  if s:cmdrange_hid != -1
    call matchdelete(s:cmdrange_hid)
  let s:cmdrange = []
  let s:cmdrange_hid = -1

Level 3

We need to implement s:get_cmdrange to get current cmd range

function s:get_cmdrange()

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 []

    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]
    call setpos('.', cpos)

function Tech_get_cmdrange() range
  let s:tech_cmdrange_line1 = a:firstline
  let s:tech_cmdrange_line2 = a:lastline

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
let s:cmdrange_pat = s:build_cmdrange_pattern()
  • Thank you very much, works but has some minor issues, e.g. if I misstype something in the range, I get a bunch of messages and have to leave the ex command and start again. But its a very good starting point which has all the bits and pieces I need. – Hans Hohenfeld Nov 9 '19 at 14:47
  • 1
    One error is caused by add and delete multiple space in the beginning of command line, I have fixed it, and some other small issues. You may find the newest version here github.com/dedowsdi/.vim/blob/master/autoload/misc/hlcmd.vim – dedowsdi Nov 9 '19 at 23:15

Finding an "easy" way.

The idea is to drop to linewise Visual mode and then get back restoring the command-line contents.

" some regex magic to match a range pattern:
" . or $ or % or number or mark or /pat/ or ?pat? or \/ or \? or \&
" optionally followed by ++/--number
let s:pat = '([.$%]|\d+|''[[:alnum:]''\[\]]|/.*/|\?.*\?|\[/?&])?([+-]+\d*)?'
" full pattern is: lineno followed by , or ; and another lineno
" nearly every field is optional though
let s:fullpat = '\v^\s*' . s:pat . '([,;]' . s:pat . ')?'
function! s:getrange(line)
    return matchstr(a:line, s:fullpat)

" use <l1>GV<l2>G to visually select range
" then restore the command line with feedkeys()
command! -range -nargs=1 ToVisual exe 'norm!' <line1> . 'GV' . <line2> . 'G' |
    \ redraw | call feedkeys(":\<C-U>" . <q-args>, 'n')

" Press <C-X> while in the command line to make a visual selection
" The command line contents will be restored automatically
cnoremap <silent><C-X> <C-\>e(getcmdtype() == ':' ?
    \ 'silent ' . <SID>getrange(getcmdline()) . 'ToVisual ' : '') .
    \ getcmdline()<CR><CR>
  • Hi Matt, thank you, I've tried it and it seems to work sometimes and sometimes not, for :s it starts higlighting once I press the / after s, for :m and :co it doesn't highlight anything. I therefore did go with the solution from @dedowsdi. – Hans Hohenfeld Nov 9 '19 at 14:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.