36

In a very long file with (accurately-) indented code, it may be helpful to move directly to a line of text above or below the cursor which delimits the indenting.

(One way of doing this could be via the indent foldmethod, by folding the current indent level, which collapses the lines delimiting the indent level to be only a j or k away from the cursor. However, I have found instances where indent folding hasn't been able to work on the indent I'm interested in.)

Here's an example, with the cursor marked by the underscore _ (at the second character column between the definitions of class Bazz and class Qux), but imagine that the number of paragraphs or lines within the modules is unknown or variable:

module Foo
  # lots of code...
end

module Bar
  class Baz
    # ...
  end

  class Bazz
    # lots of code...
  end
 _
  class Qux
    # ...
  end

  class Quux
    # lots of code...
  end
end

...how could I quickly navigate to the o of module Bar above the cursor, or the n of the end below the cursor at the very last line? Note that the vertical column contains whitespaces, but also no characters at the second column in between the class definitions.

3
  • 2
    related: "Move to next/previous line with same indentation " on Vim Tips Wiki vim.wikia.com/wiki/Move_to_next/…
    – alxndr
    Feb 24, 2015 at 3:54
  • Pressing g and then direction key might avoid tabs and move across lines
    – SibiCoder
    Apr 19, 2016 at 12:40
  • 1
    The solution in @alxndr's link works well except in the case where the whitespace is poorly maintained (e.g., there are random lines that only have whitespaces) and you want to ignore the whitespace. superjer's solution below is better if one is concerned about that case.
    – nukeguy
    Jul 10, 2017 at 22:49

8 Answers 8

10

How about this function?

function FloatUp()
    while line(".") > 1
                \ && (strlen(getline(".")) < col(".")
                \ || getline(".")[col(".") - 1] =~ '\s')
        norm k
    endwhile
endfunction

You could nnoremap gU :call FloatUp()<CR> (gU for "go up"), and then invoke with gU.

Here's an explanation:

  • The code repeatedly moves up the cursor (with norm k) as long as the while condition holds.
  • The while condition holds if this is not the first line (line(".") > 1) and either
    • this line is too short (probably empty); or
    • the current line (getline("."))'s character at the current column ([col(".") - 1]) is whitespace (=~ '\s'; i.e., matches the whitespace pattern).
3
  • 1
    This seems to work perfectly (and quickly), and is easily modified into a FloatDown function as well. Into my .vimrc it goes, thanks! github.com/alxndr/dotfiles/commit/0e25a302
    – alxndr
    Feb 4, 2015 at 17:54
  • 1
    Just discovered that this solution doesn't quite work in visual mode (slightly different behavior depending on which visual mode, it'll either stay in the same position or move by only one line). Any pointers on what's going on there?
    – alxndr
    Jun 26, 2015 at 2:30
  • I've packaged this up into my Lua config: this function (and corresponding FloatDown) wrapped in vim.cmd [[ .. ]] defined in one file github.com/alxndr/dotfiles/blob/bf37c21/nvim/lua/plugin/… and required via init.lua, and then those functions are mapped with vim.api.nvim_set_keymap() like so github.com/alxndr/dotfiles/blob/bf37c21/nvim/lua/…
    – alxndr
    Mar 27 at 23:23
18

I like using search for these things, because Vim's regexs have an insane amount of things you can search for, like specific virtual column numbers, in this case:

nnoremap cd /\%<C-R>=virtcol(".")<CR>v\S<CR>

nnoremap cu ?\%<C-R>=virtcol(".")<CR>v\S<CR>

Use cd to go down and cu to go up. I'm thinking "column up" and "column down" here. I'm almost 100% confident that these are no-ops by default. Or pick your own mappings.

This should work whether you are using tabs or spaces or both.

9
  • 1
    I'm having trouble parsing those commands. Would you mind expanding on them a bit? Feb 4, 2015 at 0:18
  • 1
    / or ? to search down or up, \%xv limits to search to virtual column number 'x', the 'x' in this case is '<C-R>=virtcol(".")<CR>' which runs the virtcol command and inserts the virtual column number the cursor is at into the expression, \S means any non-whitespace character.
    – Chad
    Feb 4, 2015 at 0:29
  • 1
    Thanks for explaining Chad. I will just add that <C-R> in Insert mode or Command-line mode grabs the contents of a register. The = register is the expression register which evaluates an expression in place. It's very handy, if not a bit weird looking.
    – superjer
    Feb 4, 2015 at 1:01
  • 2
    One disadvantage of this approach is that if you have search highlighting on, you'll find that all the characters in the cursor's vertical column are now highlighted after "column up"ing or "column down"ing.
    – alxndr
    Feb 4, 2015 at 17:47
  • 3
    If you want to protect your existing search you can prepend :let searchbak=@/<CR> and append :let @/=searchbak<CR>. But at this point it's getting kind of long and unapproachable and maybe should just be a function.
    – superjer
    Feb 5, 2015 at 1:18
9

You may use JumpToVerticalOccurrence plugin (default mapped to ]| and [|). There are few others like ]V{char} which works like f, but vertically. You can of course remap them as required.


If you don't want to use plugins, you may try to use :search with virtcol(.), e.g.:

:call search('\%' . virtcol('.') . 'v\S', 'bW')

where:

  • virtcol('.') gives you the current column,
  • \S stands for non-whitespace
  • bW is for backward search

Check :help search to learn more.

To make it easier to use, you can map it, e.g.:

:map <C-k> :call search('\%' . virtcol('.') . 'v\S', 'bW')<CR>
:map <C-j> :call search('\%' . virtcol('.') . 'v\S', 'W')<CR>

Alternatively, if your code contains brackets, use % to jump between matching them (e.g. {, }, [, ], etc.) or use whole paragraph jumps ({, } with Shift if necessary).


Links/sources:

3
  • 1
    From :h search(): w means wrap around the end of the file and W means don't wrap around the end of the file. But why did you put them together on the second :map? Apr 19, 2016 at 10:53
  • 1
    @JairLopez Correct, w shouldn't be there, so I've removed it, thanks. Probably I was confused by motion keys meaning.
    – kenorb
    Apr 19, 2016 at 11:03
  • Very elegant solution. Wrapped in Lua for Neovim: github.com/alxndr/dotfiles/blob/15d7c0f/nvim/lua/plugin/…
    – alxndr
    Mar 27 at 23:34
3

I use a modified version of the vim-indent-object plugin to provide this behavior. My modifications can be seen here.

This allows me to map [i to go to beginning of current indent level and ]i to go the end.

3

Since I don't see it here as an answer (edit just noticed that superjer mentioned something very similar). You can simply the / or ? search.

For example, you can search downward for a non whitespace character in the 2nd column like this:

/\%2v\S

The breakdown is:

/    "search downward (use ? for upward movement)
\%2v "for the following char in the 2nd virtual column
\S   "any non white space character

Then you can use the normal operators to repeat the search.

For more info on the subject see :help \%c and :help \%v.

1
  • Oh my god, literally the last answer on the page and it's the one that best answers the question. Thank you!! Oct 21, 2021 at 13:58
2

It's not exactly what you asked for because it's not vertical movement, but something that's similar, fast, and might be good enough for you is moving backward 2 paragraphs: {{. This will take you to the line right before module Bar.

2
  • 2
    Imagine there are an unknown number of lines and/or paragraphs between the cursor and its top-level module line. (Specifically, this situation arose when I was recently navigating third-party code several thousand lines long, with accurate indentation but no empty lines, and trying to find the outer context of a module's definition.)
    – alxndr
    Feb 3, 2015 at 20:25
  • Jumping by paragraphs is my preferred way of moving around, but it wasn't an option: the entire file was one "paragraph".
    – alxndr
    Feb 3, 2015 at 20:29
2

jeetsukumaran/vim-indentwise works well for relative, absolute, or block-scope movements across indented blocks.

1

I use my own plugin move_until_char_changes.vim for that.

I map it to gK and gJ.

It will jump over many lines with the same character in the current column (not just whitespace), which can be useful in logfiles or SQL dumps with repeated strings.

By default it first moves one line, before picking up the "search" character.

It works in Visual mode provided you don't try to use it twice, and you aren't in linewise or blockwise Visual mode... (fixes welcome!)

0

Your Answer

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

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