5

When I'm writing, I often have 2-3 buffers open. My main body of text in the middle, and then supporting materials on the left and right. I often read a paragraph from one of the supporting buffers then switch back to the primary to write for a while before switching back.

I'm trying to figure out how I can highlight a paragraph without selecting it. Is this the highlight command or am I barking up the wrong tree? That seems like it's related specifically to syntax highlighting, rather than being able to highlight arbitrary blocks of text.

EDIT: I should clarify that part of why I want to highlight without selecting is that the side windows might be on the same buffer, and 'locked' together rather than independently operated; one can scroll independently but they lock together if you select then switch to another. I may want to look at two different parts of the document and select mode restricts freedom. I tested this, and can't seem to open a new independent buffer on the same file; editing the same file just makes that window join the same buffer.

9
  • have a look at this question Commented Nov 11, 2019 at 8:10
  • @ChristianBrabandt i think OP wants to highlight an entire paragraph here
    – D. Ben Knoble
    Commented Nov 11, 2019 at 13:12
  • Edited for clarity
    – Harv
    Commented Nov 11, 2019 at 17:23
  • 1
    @D.BenKnoble okay - I just tried to re-produce the issue and failed -- the windows didn't lock together, so I'm not sure why they did before, though I'd like to. Still, when using visual mode select, switching to a different buffer loses the selection, so it no longer stands out from paragraphs above and below it.
    – Harv
    Commented Nov 12, 2019 at 1:16
  • 1
    @dedowsdi thank you so much for putting in that much work. I was looking for a simpler answer that would also help me figure out how vim works and why, and frankly that answer is so far over my head that I probably won't learn much from it. I was hoping there was a built in command I just couldn't find.
    – Harv
    Commented Nov 12, 2019 at 1:17

1 Answer 1

5

:h :highlight is used to define highlight group. What you want is the :h :match , :h matchadd() families.

This is how I will do it.

Goal

Highlight arbitary area without staying in visual mode, all wise type should be supported.

How to do it

The job can be split into three main parts:

  1. Select arbitary area:

    There are two ways to select arbitary area in vim.

    • Operator ( bound to ,h )
    • visual select
  2. Highlight it.

    We will use :h matchadd() to do it. We can use :h /\%l , :h /\%c to to control the highlight area precisely.

  3. Remove highlight area where the cursor is on.

    We will create a map ,hh for this. We must keep track of every highlighted area and compare current cursor position with them in order for this to work.

The result

So after finishing it, you can use ,h{motion} to highlight some area and ,hh to remove highlight under cursor.

You can also select arbitary area in any visual mode you like and use ,h to highlight it.

Build the skeleton

I call a highlight area a hblock. wnd_hblocks is a list of hblocks per window.

nnoremap ,h :set opfunc=AddHighlight<CR>g@
vnoremap ,h :<C-U>call AddHighlight(visualmode(), 1)<CR>
nnoremap ,hh :call <sid>remove_cursor_highlights()<cr>

function AddHighlight(type, ...) abort
  let visual = get(a:000, 0, 0)
  let hblock = s:create_hblock(a:type, visual)
  call s:highlight(hblock)
endfunction

function s:remove_cursor_highlights() abort
  let wnd_hblocks = s:get_wnd_hblocks( bufwinid('') )

  " clear highlight
  call map( copy(wnd_hblocks),
        \ { i,v -> s:contain( v, getcurpos() ) && matchdelete(v.hid) } )

  " clear hblocks
  call filter( wnd_hblocks, { i,v -> !s:contain( v, getcurpos() ) } )
endfunction

The data structure

We will keep track of every hblock, including it's start, end position and selection type.

" { winid: [ {hid: , start:  , end:  , wise :  }, ... ] , ...  }
" hid : highlight id return from matchadd
" start : start of cursor pos, same as getcurpos()
" end : ditto
" wise : v, V or "\<c-v>"
let s:hblocks = {}

function s:create_hblock(type, visual) abort
  if a:visual
    let wise = visualmode()
    let start = getpos("'<")
    let end = getpos("'>")
  else
    let wise = a:type ==# 'char' ? 'v' : a:type ==# 'line' ? 'V' : "\<c-v>"
    let start = getpos("'[")
    let end = getpos("']")
  endif

  return {'wise':wise, 'start':start, 'end': end}
endfunction

Rest of the book keeping and highlighting

function s:highlight(hblock) abort
  let pattern = s:build_pattern(a:hblock)

  " lazy man's debug register
  let @/ = pattern
  let a:hblock.hid = matchadd('VISUAL', pattern)
  let wnd_hblocks = s:get_wnd_hblocks( bufwinid('') )
  call insert(wnd_hblocks, a:hblock)
endfunction

function s:get_wnd_hblocks(winid)
  if !has_key(s:hblocks, a:winid)
    let s:hblocks[a:winid] = []
  endif
  return s:hblocks[a:winid]
endfunction

function s:contain(hblock, pos)
  let [l0,c0] = a:hblock.start[1:2]
  let [l1,c1] = a:hblock.end[1:2]
  let [l2,c2] = a:pos[1:2]

  if l2 < l0 || l2 > l1
    return 0
  endif

  if a:hblock.wise ==# 'V'
    return 1
  elseif a:hblock.wise ==# "\<c-v>"
    return c2 >= c0 || c2 <= c1
  else

    " not ( cursor in the upper left corner or cursor in the lower right corner)
    return !( l2 == l0 && c2 < c0 || l2 == l1 && c2 > c1 )
  endif
endfunction

function s:build_pattern(hblock)
  let [l0,c0] = a:hblock.start[1:2]
  let [l1,c1] = a:hblock.end[1:2]

  if a:hblock.wise ==# 'V'
    return printf('\v%%>%dl^.*%%<%dl', l0 - 1, l1 + 1)
  elseif a:hblock.wise ==# "\<c-v>"

    " restrict cols on all lines
    return  printf('\v%%>%dl%%>%dc.*%%<%dl%%<%dc', l0 - 1, c0 - 1, l1 + 1, c1 + 1)
  else
    if l0 != l1

      " mid lines | tail of 1st line | head of last line
      return printf('\v%%>%dl^.*%%<%dl|%%%dl%%>%dc.*|%%%dl%%<%dc.*', l0, l1, l0, c0 - 1, l1, c1 + 1)
    else

      " mid of the only line
      return printf('\v%%%dl%%>%dc.*%%%dl%%<%dc', l0, c0 - 1, l1, c1 + 1)
    endif
  endif
endfunction
2
  • 1
    Whoa. This could be a whole plugin with some clean up and attention to the mapping interface. On that note, , is too darn useful (imo) for maps. And the first should really be nnoremap
    – D. Ben Knoble
    Commented Nov 11, 2019 at 13:11
  • @D.BenKnoble Yes, it should be nnoremap . I only use , when i ; too far, which rarely happens, so I use , as the leading character to map all the custom motion and operator, it's much more handier than \ and _ , as i don't have to move my wrist. A map from ,, to , is also created, in case i really need ,.
    – dedowsdi
    Commented Nov 11, 2019 at 23:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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