13

I work on a rather large C++ project. One of the more frustrating aspects of its organization is very large functions placed inside ridiculously large files.

I often want to search for any instance of a particular global variable or function call, restricted to the current function. Is there a reasonably simple formula for accomplishing this?

(I do have ctags installed and use Tagbar... which might be helpful for this)

9

Here's how I would do it. Add this to your .vimrc

vnoremap if [[O][

Explanation: vnoremap means map the left hand side if to the right hand side [[mO][ while you're in visual mode. if means In Function, although you can rename this if you like. [[ jumps to the beginning of the function. O moves to the other end of your visually selected text, and then ][ moves to the end of the function.

So, if you want to search in a function, now you enter visual mode with v, and select the whole function with if. Now exit visual mode with <esc>, and search with /\%V. \%V restricts your search to the previously selected text. If you don't want to have to hit <esc>/\%V you could also add this to your .vimrc:

vnoremap / <esc>/\%V

Then, your sequence of keystrokes would look like:

vif/foo<enter>

and this will find all occurrences of foo in the current function.


The only downside to this method is that it expects the opening and closing braces to both have 0 indentation. If you regularly work with code that doesn't have this, e.g.

int foo() {
    bar()
}

then this slightly more complicated version will work:

vnoremap if ][ma%O'a

This only expects the closing brace to have 0 indentation. If the opening brace has indents, it still works, although it does take up a mark. If you regularly use mark 'a', you can move this e.g.

vnoremap if ][mb%O'b
vnoremap if ][mc%O'c
...
  • Alas this doesn't work well with C++ functions. Unlike C functions, they are likely to be indented (it's the case with inline members functions defined in their class definition, and with the default indentation of functions within namespaces). However your idea can be built upon thanks to function definition range that can be obtained with ctags. I do it in the function lh#dev#find_function_boundaries from lh-dev – Luc Hermitte May 24 '16 at 17:49
  • 3
    Nice approach. If you can reliably find the top line of the function, then you can move to the { and then use % to reach the bottom line. Not sure how you might find the function start in C++, but this works ok for Javascript: vnoremap if <Esc>?^\s*function<CR>v/{<CR>%o – joeytwiddle May 24 '16 at 19:56
  • 1
    Regarding your last edit. CTRL-] jumps to the tag under the cursor. Not to the start of the current function. It won't help. – Luc Hermitte May 25 '16 at 9:16
  • Regarding the new edit. It ain't that simple. The difficulty is to have vim know the current function. If it had the information, it wouldn't require ctags help. The only way to obtain the information (from ctags) is to analyse the jump-to-declaration commands produced by ctags. If these commands were :linenumber, vim could do what I do in my plugin. But there is no guarantee, and these commands can instead be search-/patterns -- Vim cannot test all patterns to know which one matches the current function.IOW, I'm not aware of any vim action to jump to the start of the current function – Luc Hermitte May 26 '16 at 15:54
6

DJ McMayhem's solution inspired me to write my own that relies on ctags and on matchit to do a proper analysis of function boundaries.

The difficult part has been already done by lh-dev and lh-tags for several years:

  • the current file is parsed through ctags with the right options
  • we look for all function definitions in the tags database which is restricted to the tags obtained for the current file
  • thanks to the DB, we have the start line numbers for all functions (well the template and inline part may be missed by ctags)
  • with a simple iterative search (a binary search could have been done, but well, files are supposed to be "short"), the beginning of the current function is found
  • And thanks to matchit plugin, its final line is found as well -- I see that universal ctags is offering a end field that can be used with C, C++, Python, and Vim that could also be used to find the end of a function.

Note that any parts of this algorithm could be overridden on a file-type basis. i.e. the boundaries detection of python functions could search for def and analyse indentation, we could just search for function in javascript, and so on -- In other words, the current version also works with Java, Vim and some other languages (I still have some work to do for Python)

So I define now two new mappings: a visual mode mapping, and an operator-pending mode mapping:

onoremap <silent> if :<c-u>call lh#dev#_select_current_function()<cr>
xnoremap <silent> if :<c-u>call lh#dev#_select_current_function()<cr><esc>gv

Which rely on:

function! lh#dev#_select_current_function() abort
  let fn = lh#dev#find_function_boundaries(line('.'))
  exe fn.lines[0]
  normal! v
  exe fn.lines[1]
endfunction

I spare you the several hundred lines of code of lh#dev#find_function_boundaries()

And thanks to DJ McMayhem's mapping

" Note that my vim settings requires two backslashes here instead of one
vnoremap / <esc>/\\%V

we can do a vif/pattern to search for pattern in the current function.

We can also delete functions with dif, yank them with yif, etc.

Here is what it looks like when applied on a realistic C++ function (i.e. not 0 indented): Screencast: Select C++ function

4

Finding the beginning and end of a function can be difficult, especially in a language without a function keyword… and many conflicting indent styles.

If your function ends with a closing brace alone on its own line (as in 10 out of the 13 styles listed here), you could visually select it with something like this:

xnoremap if /^\s*}<CR><Esc>V%

From there, searching for foo within your function is only a matter of:

:'<,'>g/foo/#

Putting it all together, we can get a rather nice mapping:

xnoremap if /^\s*}<CR><Esc>V%
nmap <key> vif:g//#<Left><Left>

search in function

That said, the visual mode mapping would probably be easily fooled by a while or an if so it would probably benefit from a bit of polishing. Also, keeping the visual selection may not be a very good idea…

  • This just selects the next block it can find. It doesn't work at all once you add if, for, while, etc. – DJMcMayhem May 26 '16 at 16:17
3

An imperfect solution is using folds. Fold everything:

set foldmethod=syntax
set foldlevel=0
set foldminlines=0

Tell Vim not to open folded areas for search results:

set foldopen-=search

And then open the fold on the function in question (zO).

Now, all hits for the searched the text in a folded region will result in Vim jumping to the fold line once, then proceeding to the next hit.

For example, in the case below:

enter image description here

The folded function has many usages of size, but n won't leading me around every usage of size in that function.

3

Another way:

  • use ctags etc. to find the target function, go there
  • move cursor forward inside the function body
  • use Osyo Manga's search operator (depends on vim-operator-user) to only search inside the current block. E.g.:

    " configure the plugin (once, vimrc):
     map g/ <Plug>(operator-search)
    
    " 1. use ctags etc. to jump to the beginning of the target function;
    " 2. move cursor inside the function definition, then:
    g/i{
    

... now you can insert your search term at the given prompt; hit n to see how the search results are limited to the current provided motion/text-object. Since this is a Vim (i.e. composable) operator, if you have a good function text-object, you don't even need to move inside the definition body before searching, but directly use smthing like g/if, or similar.

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.