For instance move from (the cursor is on the letter in bold italic, in the second column)




and then to the "@". That would be "vertical E". And the same for W (move from the initial position to the capital "L") and B (@ → L → first l).


Interesting. Vertical analogues of word motion commands. I'm not aware of any native way to do this so my first thought is to just start with regex searches. Turns out doing such searches in one step is not trivial but still viable.

First a couple notes:

  • To make it quite a bit easier I'm using (vertical) WORD motions (W,B,E) rather than word (w,b,e). The latter is [a-zA-Z0-9_]+ OR a sequence of other non-blank characters while a WORD is just \S+. In fact, your example is actually demonstrating WORD motion because otherwise the second e would end up on the L not the @. [Note: the question originally referred to w,b,e but has since been edited.]
  • The patterns will use 'very magic' flag to reduce length and improve readability.
  • To start, my pattern will be based on a fixed column number (4). This will be made variable later.

Starting with E...

  • \v - very magic
  • %4v\S - in column 4 there must be a non-whitespace character (our WORD char)
  • .* - slurp up anything remaining on the line
  • (..|..|..) - parens indicating any of the following three patterns are allowed on the next line...
  • \n.{3}\s - after the current line's newline the next line starts with three characters followed by whitespace (in the cursor column)
  • \n.{,3}\n - after the newline there are three or fewer characters total on the next line
  • %$ - there is no next line as indicated by this end of file token (allowing vertical E motion to end on the last line of the buffer)

This doesn't do us much good as a standalone pattern. The best thing to do is put it in a function. The key lines will look like:

let curcol = getcurpos()[2]
let prevcol = curcol - 1
call searchpos('\v%' . curcol . 'v\S.*\n(.{' . prevcol . '}\s|.{,' . prevcol . '}\n)')

This just looks at whatever column the cursor is currently on and inserts that and one less into the previously discussed pattern.

Combining the above pattern and similar patterns for the other motions and parameterizing by motion types W, B and E I have this:

func! VertWordMotion(motion)
    let curcol = getcurpos()[2]
    let prevcol = curcol - 1
    if a:motion == 'E'
        call searchpos('\v%' . curcol . 'v\S.*(\n.{' . prevcol . '}\s|\n.{,' . prevcol . '}\n|%$)', 'W')
    elseif a:motion == 'B'
        call searchpos('\v(%' . curcol . 'v\s.*\n|^.{,' . prevcol . '}\n|%^).{' . prevcol . '}\zs\S', 'bW')
    elseif a:motion == 'W'
        call searchpos('\v(%' . curcol . 'v\s.*|^.{,' . prevcol . '})\n.{' . prevcol . '}\zs\S', 'W')
        echohl ErrorMsg | echo 'Not a valid motion: ' . a:motion | echohl None

Finally, create mappings like these:

nnoremap <leader>E :call VertWordMotion('E')<CR>
nnoremap <leader>B :call VertWordMotion('B')<CR>
nnoremap <leader>W :call VertWordMotion('W')<CR>
  • My answer is call searchpos('\v(%' . curcol . 'v\s.*|^.{,' . prevcol . '})\n.{' . prevcol . '}\zs\S', 'W') Feb 10 '18 at 15:54
  • One thing I'd like it to do is ignore single spaces surrounded by non-whitespace characters. As it is now it slips in between spaces and, for example, it would skip from the closing bracket in getcurpos()[2] to the dot in %' . curcol, under elseif a:move == 'B', instead of stopping by in the line below if a:move == 'E'. Feb 10 '18 at 16:14
  • I'd like to keep it limited to single spaces, so it disregards comments further away. Feb 10 '18 at 16:23
  • If I'm understanding you, your wanting behavior that doesn't align with the Vim commands. Two thoughts on that. First, you'll be changing your original question which is frowned upon once a valid answer has been posted. Second, I think your original question is interesting and generally useful (I upvoted it). If instead it had quirky custom behavior that doesn't match anything familiar to other users then I think the value of the question is diminished. Might be able to help as an addendum but would rather not change what's already there (except to fix a bug, for example).
    – B Layer
    Feb 10 '18 at 16:31
  • I agree actually, still I'd like to know how it could be done because I've tried somewhat heuristically and now I'm curious about the proper way. Feb 10 '18 at 16:41

Although I think @BLayer's answer is very good, I also think it is worth mentioning the vim-columnmove plugin:

This plugin implements several keymappings to move cursor in vertical direction.

The plugin implements columnwise versions of the following motions:

  • f, t
  • F, T
  • ;, ,
  • w, b
  • W, B
  • e, ge
  • E, gE

I've used it for some time and finds it works very well.

  • What version of vim do you use? It's the first plugin I try to install, I'm on Vim 8 and I've used the built-in package loading functionality, but it doesn't work. I've followed this tutorial, I don't know if it's me or if maybe there are compatibility issues between Vim 8 and the plugin (it's been last updated in 2016). Feb 19 '18 at 18:54
  • 1
    It works for me both on Vim 8 and on neovim. Note, though, that the <meta>... bindings do not work properly in terminal Vim. But I think they work with gvim, and they do work well in neovim. My settings for the plugin are here, in case that might be helpful. Feb 19 '18 at 22:32

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.