3

Imagine my cursor is where the ^ is in this document:

{
  "first": [
    {
      "second": {
        "third": [
          "fourth"
        ]
      }
    },
    {
    ^ "second": {
        "third": [
          "fourth"
        ]
      }
    }
  ]
}

How can I move it to "first"?

In reality I have a thousands-lines-long json and would like to see what section I'm currently inside of.

2
  • 3
    This works for your special case since it's a json: use va[ to select the whole [] after first, then use % to jump to [ and use Esc to exit visual mode.
    – Haoshu
    Commented Jan 13, 2022 at 8:59
  • Indeed - when I have to make a navigation like this, careful use of % will usually get where you want to go quickly enough. From your highlighted position, %j% will get you to the line with first, and ^ will get you to the start. Starting from the previous item in the list, you would do %j%j%^ - obviously this doesn't scale great if the json or the motion is very complicated. Commented Jan 13, 2022 at 14:35

4 Answers 4

4

Use folds. From :h fold-commands:

[z
Move to the start of the current open fold. If already at the start, move to the start of the fold that contains it. If there is no containing fold, the command fails. When a count is used, repeats the command [count] times.

With folding enabled and all folds open, 2[z moves to column 1 of line 2. If you didn't know that you were two levels deep, then just keep typing [z until you reach the level you want.

You can also use other fold navigation methods to traverse the structure.


To enable folding support and to restrict fold-upon-open (which can be expensive for large structured files), add the following to your .vimrc:

set foldmethod=syntax foldlevelstart=99

The depth value of "99" is arbitrarily high. You may customize that value to suit your files' specific shape.

9
  • 2
    I imagine this pairs well with foldmethod=indent?
    – D. Ben Knoble
    Commented Jan 13, 2022 at 14:47
  • @D.BenKnoble Yes, and also foldmethod=syntax, at least in my experience.
    – bishop
    Commented Jan 13, 2022 at 14:50
  • 2
    I recommend mention those both in the answer then
    – D. Ben Knoble
    Commented Jan 13, 2022 at 15:05
  • 1
    Is there a way to use this fold movement without ever having closed folds?
    – user9024
    Commented Jan 13, 2022 at 15:15
  • 1
    @theonlygusti Yes, you can start vim with folds fully open. I've updated my answer accordingly.
    – bishop
    Commented Jan 13, 2022 at 16:52
2

You can use the following plugin to jump to the different levels of indent:

https://github.com/jeetsukumaran/vim-indentwise

Also, refer to https://vim.fandom.com/wiki/Move_to_next/previous_line_with_same_indentation How do I jump to the next line with the same indent level?

2

Another option here is to search for the matching [ that starts the JSON array you're inside. That isn't exactly what you asked (“nearest upward smaller level of indentation”), but in a way it can be better since it doesn't depend on proper indentation of the JSON contents.

Vim has the [{ command to find the nearest unmatched {. For the nearest [, there's not a ready-made command you can use directly ([[ is used to navigate "sections" which is something different), but you can build your own using the searchpair() function.

At the simplest, you can use the following command to find the nearest unmatched [:

:call searchpair('\[', '', '\]', 'b')

You can make this more robust, by passing searchpair() an extra {skip} argument to skip matches that happen inside strings (in which case, you can use the JSON syntax elements to determine whether a match is inside a string), and pass some extra flags s to mark the spot before the jump (so you can use Ctrl-O to quickly jump back) and W to prevent wrapping around the other end of the file.

You can then create mappings for the command, in both directions:

nnoremap <buffer> <silent> [[ :call searchpair('\[', '', '\]', 'bsW', 'synIDattr(synID(line("."), col("."), 0), "name") =~# "^\\(jsonKeyword\\|jsonString\\)"')<CR>
nnoremap <buffer> <silent> ]] :call searchpair('\[', '', '\]', 'sW', 'synIDattr(synID(line("."), col("."), 0), "name") =~# "^\\(jsonKeyword\\|jsonString\\)"')<CR>

These are <buffer> mappings, so you can add them to a file ~/.vim/ftplugin/json.vim and they'll be available in JSON files only.

There are possible improvements to these mappings. For example, [{ takes a count to find the Nth unmatched { and these mappings don't take one (and that can be useful, for instance 99[{ will typically take you to the outermost unmatched {.) You could implement that in a function that calls searchpair() repeatedly up to v:count times.

These mappings also don't work in Visual mode, and they might be useful there if you want to select the whole block (e.g. v[{o]} will select the block delimited by curly braces.)

But hopefully the knowledge of searchpair() and the skip expression and the sample naïve mappings will get you started on finding ways to improve on it that will get you the exact features you need.

1

This can be achieved by searching previous lines that start with fewer spaces. Map this and use the mapped keys (\aa in this case) to achieve what you want:

:map \aa 0"ay^?\(^ *\S\)\&\(^<C-R>a\)\@!<CR>:nohlsearch<CR>

Explanation:

  • :map \aa: map the whatever after it to \aa.
  • 0"ay^: copy the leading spaces (from 0 to ^) to a.
  • ?: search for previous line that match the pattern.
  • \(^ *\S\)\&\(^<C-R>a\)\@!: match the lines with fewer spaces.
    • \(^ *\S\): match the lines that start with 0 or more spaces, and then a non-space (\S).
    • \&: and
    • \(^<C-R>a\)\@!: doesn't match the lines start with equal (or more) number of spaces with current line.
      • <C-R>a: hit <C-R> and then a to paste the copied leading spaces from a.
      • \@!: not match previous \(^<C-R>a\).
  • <CR>: hit enter key to execute the search.
  • :nohlsearch<CR>: stop the highlight search.

And since it's a json, an alternate approach is to use va[ to select the whole [] after first, then use % to jump to [ and use Esc to exit visual mode.

1
  • 1
    Prefer nnoremap and <leader>, and setting the register is unfortunate, but otherwise great idea!
    – D. Ben Knoble
    Commented Jan 13, 2022 at 13:09

Your Answer

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