lervag / vimtex

VimTeX: A modern Vim and neovim filetype plugin for LaTeX files.
MIT License
5.53k stars 391 forks source link

Add motions to jump to the beginning/end of the _surrounding_ environment #1299

Closed dbankmann closed 5 years ago

dbankmann commented 5 years ago

First, I really like the motions [m, [M, etc. . However, sometimes when writing up my latex stuff, they are a bit counter-intuitive. Sometimes, I really have long equation/align environments with lots of subenvironments. It is not really feasible to count all of that subenvironments to simply go to the end of the surrounding environment.

It would be nice to have additional motions that allow me to go to the beginning/end of surrounding environments. In principle you could just do vae<ESC> and vae<<ESC>, but that's a bit cumbersome.

kiryph commented 5 years ago

To clarify your feature request: you want to jump directly to the begin or end of the highest level surrounding environment, most likely with the exception of \begin{document}\end{document}.

When we consider following example file

\documentclass{article}

\begin{document}
  \begin{center}
      \begin{align}
        a = b (cursor)
      \end{align}
  \end{center}
\end{document}

you want to navigate directly to the lines with \begin{center} or \end{center}.

I have a few thoughts about what you said and what I use:

dbankmann commented 5 years ago

Wow, thanks for all the input. There's definitely some stuff I gonna try out. Especially, the repmo plugin sounds useful. And no, I didn't really know of extending selections. That's pretty useful, as long as I'm moving forwards.

To clarify your feature request: you want to jump directly to the begin or end of the highest level surrounding environment, most likely with the exception of \begin{document}\end{document}.

That's only half of the truth. I want to be able to move between beginnings/ends of different levels of environments (in the current buffer). I guess you would need 3 different movements for each type of movement (forwards/backwards , to the end/beginning of environment), for example for moving to the next endings:

(I know that the proposed mappings are bad choices, this is just for illustration) Let me show this with your (updated) example:

 \documentclass{article}

 \begin{document}
   \begin{center}
     \begin{someenv}              
       Some text                        (cursor)
       \begin{align}
         a = b                                      
         \begin{matrix}
              a & b\\
              c & d
         \end{matrix}                   ]M
         \begin{matrix}
              a & b\\
              c & d
         \end{matrix}                   ]M]M 
       \end{align}                       ]C or ]M]M]M
       Some text
       \begin{align}

         \begin{matrix}
              a & b\\
              c & d
         \end{matrix}
         a = b 
       \end{align}                          ]C]A
    \end{someenv}                      ]A or ]M;;;; with repmo
   \end{center}                            ]B
 \end{document}
kiryph commented 5 years ago

The extended example is very helpful to understand you better.

I can point you to a few more related motions:

  1. MOVING BASED ON FOLDS [z ]z zj zk ]z moves to the end of the current fold. If you have enabled folding for environments, you can move in your example to the line marked with ]A by ]z. However, if there is another type of fold it could interfer.

  2. PLUGIN MATCHUP [% ]% The plugin vim-matchup defines [% and ]%. This allows you to move in the given example to the line marked with ]A by pressing ]% also when folding is disabled. However, if you are within parentheses, brackets, etc, it also requires several presses.

    Note if you install vim-matchup, you have to tell it that it should overwrite vimtex:

    :h matchup-latex

    By default, match-up is disabled for tex files when the plugin |vimtex| is detected. To enable match-up for tex files, use the following in your vimrc:

    let g:matchup_override_vimtex = 1
  3. MOTIONS BASED ON INDENTATION Maybe motions based on indentation could be another alternative which offer following plugins:

  4. GENERAL MOTIONS I am also not sure if you actually want your cursor on the \end{...} lines eventually (they hardly change). You probably need another motion for your final target anyway. Therefore, consider also other means for navigation, e.g. navigation by searching (/target text) and the popular plugins

dbankmann commented 5 years ago

That's getting very close to what I wanted! At least the mapping for ]C looks perfectly fine. The problem I see with the ]z mappings is that they behave quite differently to the ]m mappings. The folds are somehow defined by the inner part of an environment, whereas the ]m mappings use the outer part as well. For example, pressing ]z when at a \begin{env} line will not bring me to the \end{env} line of that environment. When there is no more nested environments inside env, ]m would do that and so would my ideal mapping.

This allows you to move to the line marked with ]A by pressing ]% also when folding is disabled. This does not work for me without vim-matchup. This is the mapping without the plugin: ]% * :<C-U>call <SNR>77_MultiMatch("W", "n") <CR>

I am also not sure if you actually want your cursor on the \end{...} lines eventually (they hardly change). >You probably need another motion for your final target anyway. Therefore, consider also other means >for navigation, e.g. navigation by searching (/target text) and the popular plugins

  • Yes, I 'd like to end on the \begin/ \end{...} lines eventually. Then I can start writing my stuff with O\o
  • The popular navigation plugins seem to be quite nice, however I would prefer a more systematic/natural approach (sometimes for example, environments of the same type nest or nested environments have similar names (columns vs column))

//Edit: I got a bit confused by all the edits ;) So my answer may not be 100% directed to your latest edit. I'm going to review this later today.

kiryph commented 5 years ago

Sorry for the edits. I was getting a little bit too optimistic. However, a water-proof solution needs more than nmap ]A [m% which you probably could have done yourself anyway. So I just reduced my comment to the basic ingredients I was considering.

Vim-Ninja-Feet

Yes, I 'd like to end on the \begin/ \end{...} lines eventually. Then I can start writing my stuff with O\o

I see.

I am starting to laugh about myself: there is another plugin you might find interesting: vim-ninja-feet

This plugin allows you to enter insert mode before or behind a text object or motion, including the ones from vimtex.

Example: When we consider again your example, press z]ae to enter insert mode after \end{someenv}. If ae would operate linewise, a new line would be opened instead of just entering insert mode. Try for example z]]z which opens a line before ]A line.

I have played with this as well and extended vim-ninja-feet by Z[ and Z] to enforce opening always a newline regardless of the nature of the text object. Apply following patch to try Z]ae for yourself:

❯ g diff
diff --git a/plugin/ninja-feet.vim b/plugin/ninja-feet.vim
index 661a641..1797bf9 100644
--- a/plugin/ninja-feet.vim
+++ b/plugin/ninja-feet.vim
@@ -37,6 +37,16 @@ function! s:ninja_append(mode)
        call feedkeys('`]'.op, 'n')
 endfunction

+function! s:ninja_open_above(mode)
+       let op = 'O'
+       call feedkeys('`['.op, 'n')
+endfunction
+
+function! s:ninja_open_below(mode)
+       let op = 'o'
+       call feedkeys('`]'.op, 'n')
+endfunction
+
 function! s:map_expr(sid, type, direction, count)
        let map = ''
        let map .= "\<Esc>"
@@ -58,6 +68,8 @@ onoremap <silent> <expr> <Plug>(ninja-right-foot-a)     <SID>map_expr("<SID>", '

 nnoremap <silent> <Plug>(ninja-insert) :<C-U>set operatorfunc=<SID>ninja_insert<CR>g@
 nnoremap <silent> <Plug>(ninja-append) :<C-U>set operatorfunc=<SID>ninja_append<CR>g@
+nnoremap <silent> <Plug>(ninja-open-above) :<C-U>set operatorfunc=<SID>ninja_open_above<CR>g@
+nnoremap <silent> <Plug>(ninja-open-below) :<C-U>set operatorfunc=<SID>ninja_open_below<CR>g@

 if !exists('g:ninja_feet_no_mappings')
        call s:map('[i', "<Plug>(ninja-left-foot-inner)", 'o')
@@ -67,4 +79,6 @@ if !exists('g:ninja_feet_no_mappings')

        call s:map('z[', "<Plug>(ninja-insert)", 'n')
        call s:map('z]', "<Plug>(ninja-append)", 'n')
+       call s:map('Z[', "<Plug>(ninja-open-above)", 'n')
+       call s:map('Z]', "<Plug>(ninja-open-below)", 'n')
 endif
kiryph commented 5 years ago
nmap [C [M%
nmap ]C ]m%

At least the mapping for ]C looks perfectly fine.

Great.

I have tried another version for ]A and ]B based on text objects:

nmap [A vaeo<Esc>
nmap ]A vae<Esc>
nmap [B vaeaeo<Esc>
nmap ]B vaeae<Esc>

Could you give it a spin?

kiryph commented 5 years ago

A more polished version with a different choice of mappings ([+,[=,[-,]-,]=,]+) which I would be inclined to use by myself:

~/.vim/after/ftplugin/tex.vim:

" ===================================================================
" Jump to the start/end of the current/surrounding/nested environment
" ===================================================================
" https://github.com/lervag/vimtex/issues/1299
" uses ae, [M, and ]m from vimtex

" CURRENT SURROUNDING ENVIRONMENT
noremap <buffer><silent> <Plug>(move-begin-current-env) :normal vaeoV<CR><Esc>
noremap <buffer><silent> <Plug>(move-end-current-env) :normal vaeV<CR><Esc>

" PARENT SURROUNDING ENVIRONMENT
noremap <buffer><silent> <Plug>(move-begin-surr-env) :normal vaeaeoV<CR><Esc>
noremap <buffer><silent> <Plug>(move-end-surr-env) :normal vaeaeV<CR><Esc>

" PREV/NEXT NESTED ENVIRONMENT
noremap <buffer><silent> <Plug>(move-begin-prev-nested-env) :normal $[MvaeoV<CR><Esc>
noremap <buffer><silent> <Plug>(move-end-next-nested-env) :normal ]mvaeV<CR><Esc>

" Mappings [+,[=,[-,]-,]=,]+
" choice stolen from indentwise
map <buffer> [= <Plug>(move-begin-current-env)
map <buffer> ]= <Plug>(move-end-current-env)
map <buffer> [+ <Plug>(move-begin-surr-env)
map <buffer> ]+ <Plug>(move-end-surr-env)
map <buffer> [- <Plug>(move-begin-prev-nested-env)
map <buffer> ]- <Plug>(move-end-next-nested-env)

" Repmo (not entirely clear)
map <expr><buffer> [= repmo#Key('<plug>(move-begin-current-env)', '<plug>(move-end-current-env)')|sunmap <buffer> [=
map <expr><buffer> ]= repmo#Key('<plug>(move-end-current-env)', '<plug>(move-begin-current-env)')|sunmap <buffer> ]=
map <expr><buffer> [+ repmo#Key('<plug>(move-begin-surr-env)', '<plug>(vimtex-]m)')|sunmap <buffer> [+
map <expr><buffer> ]+ repmo#Key('<plug>(move-end-surr-env)', '<plug>(vimtex-[M)')|sunmap <buffer> ]+
map <expr><buffer> [- repmo#Key('<plug>(move-begin-prev-nested-env)', '<plug>(move-end-surr-env)')|sunmap <buffer> [-
map <expr><buffer> ]- repmo#Key('<plug>(move-end-next-nested-env)', '<plug>(move-begin-surr-env)')|sunmap <buffer> ]-
\documentclass{article}

\begin{document}                 % [+[+  (FIX POINT)

  \begin{center}                 % [+
    more text to come
    \begin{someenv}              % [=    (FIX POINT)
      \begin{align}              % [-    (FIX POINT)
        a = b                                      
        \begin{matrix}           % [m[m
             a & b\\
             c & d
        \end{matrix}
        \begin{matrix}           % [m
             a & b\\
             c & d
        \end{matrix}             % [M[M
      \end{align}                % [M
      more text

      Some text                  % CURSOR

      more text
      \begin{align}              % ]m
        a = b                                      
        \begin{matrix}           % ]m]m
             a & b\\
             c & d
        \end{matrix}             % ]M
        \begin{matrix}
             a & b\\
             c & d
        \end{matrix}             % ]M]M 
      \end{align}                % ]-
      more text
      \begin{align}
        \begin{matrix}
             a & b\\
             c & d
        \end{matrix}
        a = b 
      \end{align}                % ]-]-  (FIX POINT)
    \end{someenv}                % ]=    (FIX POINT)
    more text to come.
  \end{center}                   % ]+

\end{document}                   % ]+]+  (FIX POINT)

Try them also with vim-ninja-feet:

z]]=
z]]+
z]]-

However, still some issues, features (e.g. support of counts) and clarifications (e.g. repeat [-, ]- with repmo) remain.

dbankmann commented 5 years ago

I am starting to laugh about myself: there is another plugin you might find interesting: vim-ninja-feet Indeed, an interesting plugin. I'm just wondering, whether its really easier/saving keystrokes vs. motion + o/O

Thanks a lot for the snippet. I tried it for a bit, and in principle it seems to do the right thing. A few things I noted:

I'm going to have a deeper look tomorrow.

kiryph commented 5 years ago

Vim-ninja-feet

Indeed, an interesting plugin. I'm just wondering, whether its really easier/saving keystrokes vs. motion + o/O

I agree. z] and z[ from vim-ninja-feet are only really useful for situations where there is only a text object not an equivalent motion. Since there is right now no real equivalent for ae and you also want to open a new line, I think this situation is the perfect candidate for vim-ninja-feet.

This would be even more perfect if vimtex's text object would support counts and targets.vim (if used). See also the end of this comment.

Similar Motion Equivalents

As already pointed out vimtex enables by default folding for environments which means ]z can move to the end of the current environment. It also accepts counts which is useful to have the motion equivalent of your ]B. Therefore consider again this:

]A = ]z
]B = 2]z
]C = ]m]z

I also mentioned ]% from vim-matchup which also accepts counts:

]A = ]%
]B = 2]%
]C = ]m%

I think both options are not too complicated and could help you out in many situations.

repmo-vim can be used as well to go interactively to the correct level you are interested in:

The other direction works equally well.

New Dedicated Motions

Back to the new motions:

  • When pressing [= I end up at the b of the begin{...} line when the repmo mappings are not used. If I enter the command manually, it works as expected.

There was a hidden space in my snippet. Following does not contain a space at the end of line:

map <buffer> [= <Plug>(move-begin-current-env)
  • Maybe motions going to the end of an environment could also end at the beginning of \end{...}, so appending [M might make sense.

This is possible by inserting a B into the expression:

noremap <buffer><silent> <Plug>(move-end-current-env) :normal vaeBV<CR><Esc>
noremap <buffer><silent> <Plug>(move-end-surr-env) :normal vaeaeBV<CR><Esc>
noremap <buffer><silent> <Plug>(move-end-next-nested-env) :normal ]mvaeBV<CR><Esc>
  • There is some visual noise caused by going to visual mode for a few milliseconds. I guess there is no easy way to fix this?

I tried to enforce linewise operation by adding V. Does it help to remove the noise when V is not appended?

noremap <buffer><silent> <Plug>(move-begin-current-env) :normal vaeo<CR><Esc>
noremap <buffer><silent> <Plug>(move-end-current-env) :normal vaeB<CR><Esc>
noremap <buffer><silent> <Plug>(move-begin-surr-env) :normal vaeaeo<CR><Esc>
noremap <buffer><silent> <Plug>(move-end-surr-env) :normal vaeaeB<CR><Esc>
noremap <buffer><silent> <Plug>(move-begin-prev-nested-env) :normal $[Mvaeo<CR><Esc>
noremap <buffer><silent> <Plug>(move-end-next-nested-env) :normal ]mvaeB<CR><Esc>
  • The repmo stuff seems to work. Although [=, ]= has no effect at the moment.

Well, for me pressing ; and , do the same as pressing % which is also not really helpful.

  • I guess, repeating the [= ]= should move over to the next env of the same level (which they currently are not)

This probably needs more logic and an actual function.

Right now I am not up to implement them correctly: there are even more things to consider e.g. they also do not work in visual mode, do not support counts and do not update jump history (maybe easily solved by prepending m`).

Support for counts and targets.vim of ae

However, I would like to see vimtex improved in following two ways:

  1. text objects of vimtex should support counts
  2. if a user has installed targets.vim, activate text objects based on targets.vim (similar to line-targets.vim) to support the specifier n and l (next and previous). This is probably more complicated.

This would allow you to open new lines at the desired lines with vim-ninja-feet:

No need to find unoccupied normal mode motion commands which is notoriously difficult without someone complaining about the new mappings.

lervag commented 5 years ago

Hi guys. Sorry for not participating here sooner, life's been very busy lately. I'll read up on the issue when I get the time.

lervag commented 5 years ago

I've added support for counts to the environment text objects (e.g. ie, i$ and id). I can probably also add this for sections and perhaps also commands. Not sure how important you feel those TOs are wrt. this feature?

@kiryph Do you know what it would require to support targets.vim for the n and l specifiers?

kiryph commented 5 years ago

@lervag Thank you for adding support for counts.

Could you mention this in the documentation? In particular, if not all of them accept counts.

I can probably also add this for sections and perhaps also commands. Not sure how important you feel those TOs are wrt. this feature?

Do you know what it would require to support targets.vim for the nand l specifiers?

I quote https://github.com/wellle/targets.vim/blob/master/plugins.md#plugins

It's possible to extend targets.vim by adding new sources. Text objects implemented as targets.vim plugin will automatically gain all the functionality of the built in ones. Most notably you can use n and l to select next and last one, provide counts to pick a specific one, use seeking (vix pick current, next or last), growing (vixix is the same as v2ix etc.) and skipping vinxinx is the same as v2inx).

This extensibility feature of targets.vim is documented via this file https://github.com/wellle/targets.vim/blob/master/plugins.md and the plugin https://github.com/wellle/line-targets.vim serves as a tutorial. The documentation of targets.vim is not relevant and is dedicated to the 'end-user'.

So in order to understand how this works following files should help:

The builtin objects in targets.vim are apparently implemented the same way and could serve as another source to figure things out.

What do you think?

dbankmann commented 5 years ago

I tried to enforce linewise operation by adding V. Does it help to remove the noise when V is not >appended?

No, it doesn't. I guess that's because of switching to visual mode in general. Anyway, I can definitely live with the current suggestion, although your last suggestion using targets.vim and motions sounds very appealing.

lervag commented 5 years ago

Could you mention this in the documentation? In particular, if not all of them accept counts.

Yes, but I'll first finish the implementations. If you have any suggestions for how to best update the docs, that'll be helpful.

I guess it depends on the individual. For instance, I would have said support for i$ is not so important because I rarely nest inline equations like this $a=b \text{$a$ relates to $b$}$. Nested commands occur very often. The different section levels are nested more or less always.

Support for i$ came automatically with support for ie and id. But I agree it does not really make sense.

Also consistency would be a reason for me to add this. I do not want to recall which ones support counts and which ones do not.

Agreed, and good point. I'll work on it.


Regarding support for targets.vim, I'm not so sure. I'll look into it if there is an easy way to use/adapt the current code and to use targets.vim if available, though. If it is not so hard, then it seems worth the effort. Thanks for the hints and relevant links.

lervag commented 5 years ago

I'll add for iP/aP as well when I get the time. Not it should work for all the other text objects.

kiryph commented 5 years ago

Support for i$ came automatically with support for ie and id. But I agree it does not really make sense.

I am not sure if makes no sense but as I said I think it occurs rarely.

I'll add for iP/aP as well when I get the time. Note it should work for all the other text objects.

Thanks a lot.

lervag commented 5 years ago

I'm closing this since the remaining task is covered by #1315.