14

Vim provides very useful motion commands to jump to next start/end of a method: ]m, ]M, [m and ]m.

These works for Java or similar structured language. (as described in :help ]m and :help 29.3)

It seems to work considering the outermost pair of curly braces as class declaration and the next level of curly braces as method declarations.

These motion commands doesn't work when there is an outer pair of curly braces around class definition, which is somewhat common on languages as C#.

I was wondering if there is some trick to make those commands (alone and prefixed with operators, e.g., y[m, V]M) work on this code:

namespace ABC.DEF
{
    class A
    {
        protected string strID;
        public string PortID { get { return strID; } set { strID = value; } }

        protected MyType x;
        public MyType X
        {
            get { return x; }
            set { x = value; if ( x != null ) func1(); }
        }


        int func1()
        {
            return 1;
        }

        int func2(int flag)
        {
            if (flag == 0)
                return flag;


            if (flag > 3)
            {
                return flag;
            }
            return 2;
        }

        int func3()
        {
            return 3;
        }
    }
}
mMontu
  • 8,983
  • 4
  • 38
  • 53
  • For Java development, if you like Vim style commands in your IDE, IntelliJ and other IDEs have Vim plugins which offer these commands. Maybe there is a Visual Studio plugin that would have this command available and work as expected. Which IDE are you using? Or do you just use Vim from the command line? – Jesse Webb Jul 27 '11 at 18:59
  • @Gweebz - I'm using gVim to edit code, so I'd rather prefer a Vim plugin :) – mMontu Jul 27 '11 at 19:08

5 Answers5

5

I don't think the ]m family of mappings can be customized. In such cases, the usual practice is to override it with custom logic. I came up with some vimscript that should do what you describe. Basically, it jumps through curly braces and looks at the relevant line to decide what to do. In this case, it just ignores "class" and "namespace" declarations.

nnoremap <buffer> ]m :<c-u>call <SID>JumpMethod('{', 'W',  'n')<cr>
nnoremap <buffer> [m :<c-u>call <SID>JumpMethod('{', 'Wb', 'n')<cr>
nnoremap <buffer> ]M :<c-u>call <SID>JumpMethod('}', 'W',  'n')<cr>
nnoremap <buffer> [M :<c-u>call <SID>JumpMethod('}', 'Wb', 'n')<cr>

xnoremap <buffer> ]m :<c-u>call <SID>JumpMethod('{', 'W',  'v')<cr>
xnoremap <buffer> [m :<c-u>call <SID>JumpMethod('{', 'Wb', 'v')<cr>
xnoremap <buffer> ]M :<c-u>call <SID>JumpMethod('}', 'W',  'v')<cr>
xnoremap <buffer> [M :<c-u>call <SID>JumpMethod('}', 'Wb', 'v')<cr>

onoremap <buffer> ]m :<c-u>call <SID>JumpMethod('{', 'W',  'o')<cr>
onoremap <buffer> [m :<c-u>call <SID>JumpMethod('{', 'Wb', 'o')<cr>
onoremap <buffer> ]M :<c-u>call <SID>JumpMethod('}', 'W',  'o')<cr>
onoremap <buffer> [M :<c-u>call <SID>JumpMethod('}', 'Wb', 'o')<cr>

function! s:JumpMethod(char, flags, mode)
  let original_cursor = getpos('.')

  if a:mode == 'v'
    normal! gv
  elseif a:mode == 'o'
    normal! v
  endif

  while search(a:char, a:flags) > 0
    if a:char == '}'
      " jump to the opening one to analyze the definition
      normal! %
    endif

    let current_line = line('.')

    if getline(current_line) =~ '^\s*{'
      " it's alone on the line, check the above one
      let method_line = current_line - 1
    else
      let method_line = current_line
    endif

    let method_line_body = getline(method_line)

    if method_line_body =~ '\k\+\s*(.*)' && method_line_body !~ '\<\(for\|foreach\|if\|while\|switch\|using\|catch\|get\|set\)\>'
      " it's probably a function call

      if a:char == '}'
        " we need to go back to the closing bracket
        normal! %
      endif

      echo
      return
    else
      if a:char == '}'
        " we still need to go back to the closing bracket
        normal! %
      endif
    endif
  endwhile

  " if we're here, the search has failed, restore cursor position
  echo
  call setpos('.', original_cursor)
endfunction

Bear in mind that I don't really know a lot of C#, so it might not work properly in all cases, but if you give me examples that break, I might be able to figure something out.

To try it, you should put it somewhere under "ftplugin" in your vimfiles directory, as "cs.vim". Any other filename that starts with "cs" and ends in ".vim" is good too, if you already have a "cs.vim" file there.

Andrew Radev
  • 3,982
  • 22
  • 35
  • I also doesn't know much of C#, but I've tested it on the code I'm working and it seemed to worked fine, except for doesn't skipping curly braces pairs which are inside methods/properties definition. I've update the question to include such code. Thanks for your help – mMontu Jul 28 '11 at 13:16
  • Hence the ctags requirement... Testing against control statements would be easy, but if C# accepts isolated {}-blocks (like in C or C++ to restrain a scope), it will become quite cumbersome to parse the code by hand in viml, take comments into consideration, etc. – Luc Hermitte Jul 28 '11 at 13:28
  • 1
    I've edited the condition a bit, and it seems to work for your new example. @luc-hermitte is generally right, though, the only _completely_ sure way to work with methods is through ctags. This means a lot more setup and updating problems, however. My script might fail for a few edge cases, which would still be completely fine for me, but if you have a good ctags setup, you could probably benefit from trying out his plugins as well. – Andrew Radev Jul 28 '11 at 14:19
  • I've added a few more keywords to the condition (...if\|while\)\>' to ...if\|while\|switch\|using\|catch\|get\|set\)\>') and tested with some code, and it seems to work fine. Thanks! @Luc-Hermitte, I agree with Andrew previous comment, your solution is probably more accurate but requires more setup. For now I will stick to Andrew's solution, as it fulfills my needs. Thanks for your help. – mMontu Jul 28 '11 at 16:47
  • Is it possible to have these motion commands work with operators? Like "y]m" to copy from cursor to the next start of method, or "V[m" to select lines from cursor to previous start of method? Using the current solution these operations doesn't work, it seems that Vim use the default m, ]M, [m, ]m when prefixing operators. – mMontu Jul 28 '11 at 17:16
  • 1
    Edited again to add visual and operator-pending mode mappings. – Andrew Radev Jul 29 '11 at 06:49
  • 1
    This is so awesome that I made it into a plugin that applies it to more languages: [jumpmethod](https://github.com/idbrii/vim-jumpmethod). – idbrii Mar 28 '18 at 22:08
3

OmniSharp now has :OmniSharpNavigateUp and :OmniSharpNavigateDown. It always goes to "start" (hence this is for ]m and [m mappings). I'm using these mappings (taken from another answer to this question) in ftplugin\cs.vim

nnoremap <buffer> ]m :OmniSharpNavigateDown<cr>
nnoremap <buffer> [m :OmniSharpNavigateUp<cr>
nnoremap <buffer> ]M :OmniSharpNavigateDown<cr>
nnoremap <buffer> [M :OmniSharpNavigateUp<cr>

xnoremap <buffer> ]m :OmniSharpNavigateDown<cr>
xnoremap <buffer> [m :OmniSharpNavigateUp<cr>
xnoremap <buffer> ]M :OmniSharpNavigateDown<cr>
xnoremap <buffer> [M :OmniSharpNavigateUp<cr>

onoremap <buffer> ]m :OmniSharpNavigateDown<cr>
onoremap <buffer> [m :OmniSharpNavigateUp<cr>
onoremap <buffer> ]M :OmniSharpNavigateDown<cr>
onoremap <buffer> [M :OmniSharpNavigateUp<cr>
bybor
  • 153
  • 3
  • 9
2

Andrew Radev gave an answer which idbrii turned into a plugin.

I have modified that plugin here: https://github.com/RobertCWebb/vim-jumpmethod/

I fixed various issues and added improved mappings for [[ and ]] as well

  • Fixed: failed to stop when function heading was split over multiple lines (this happens all the time for me)
  • Fixed: stopped at multi-line if-statements when it shouldn't
  • Fixed: failed to stop at template functions, like Func()
  • Fixed: failed to stop at functions with comments containing words like "if" or "for"
  • Fixed: failed to stop at functions with comment lines between the function name and the brace
  • Fixed previous position marker not being set, ie after using [m or ]m you should be able to use '' to go back to previous line
  • Also, when searching back, I made it scroll up a little so the function name is visible, even though the cursor still lands on the '{'
  • Added mappings for [[, ]], [] and ][ which stop at class, enum and property definitions in addition to functions. Their standard behaviour in vim is often useless for C#. [m and ]m still only stop at methods and functions

Regarding the latter, I found it's more useful to stop at class definitions and properties too.

Properties in C# can contain a get and a set method, but I decided it was probably sufficient to just stop once at the definition of the property itself.

For classes, I noticed that I would jump from a method in one function to a method in another, without even realising I was in a different class. That seems dangerous to me, so it's nice to stop at the class definition too. The [[ and ]] mappings will do this.

Robert Webb
  • 115
  • 6
  • 1
    Just fixed a few issues and added mappings for gd and gD which work much better than vim's defaults for C++ and C# – Robert Webb Feb 20 '20 at 13:48
2

A few weeks ago, a similar question has been asked on vim mailing list, but for C++. Here is the solution I came up with.

It relies on ctags and a few other plugins of mine: lh-dev, lh-tag, and lh-vim-lib. You can install lh-dev via vim-addon-manager, this will install lh-tag and lh-vim-lib in turn.

HTH,

Luc Hermitte
  • 31,979
  • 7
  • 69
  • 83
0

You can use 9]}. It's a bit forced, but hey, it works.

Andrea Bergia
  • 5,502
  • 1
  • 23
  • 38
  • As far I understand, 9]} will just jump to the 9th '}' below, not to start or end of next/previous method. – mMontu Jul 28 '11 at 13:20
  • It jumps to the 9th enclosing }, which is generally the end of the current method. It's not the start of the next method, but it is "close enough" for me. – Andrea Bergia Jul 28 '11 at 13:22
  • 5
    The 9th } can also be the end of the class/namespace. It won't solve OP question. – Luc Hermitte Jul 28 '11 at 13:31