Vim Tips Wiki
(adding command so that you can search and fold with one command, also correcting to use setlocal rather than set so it only acts on the current window)
(→‎Search method: use the new context argument I also added)
(16 intermediate revisions by 4 users not shown)
Line 1: Line 1:
  +
{{TipImported
{{Duplicate|77}}
 
{{Tip
 
 
|id=282
 
|id=282
  +
|previous=280
|title=Folding with Regular Expression
 
  +
|next=283
|created=July 11, 2002 20:55
+
|created=2002
 
|complexity=basic
 
|complexity=basic
|author=Chris Butler (cz_butler--AT--yahoo.com)
+
|author=Chris Butler
 
|version=6.0
 
|version=6.0
 
|rating=54/23
 
|rating=54/23
  +
|category1=Folding
|text=
 
 
|category2=Searching
 
}}
 
}}
 
When searching with <code>/</code>, it would sometimes be nice to fold everything except for matches to your search. The following code does this, providing two levels of folding to allow you to show some context around each search match as well.
   
  +
==Line-based method==
When searching with /, it would sometimes be nice to fold everything except for matches to your search. The following code does this, providing 2 levels of folding to allow you to show some context around each search match as well:
 
  +
===Using a '/' search and key mapping===
  +
Add the following to your [[vimrc]] to provide a mapping to fold on an already-performed search:
   
 
<pre>
 
<pre>
map \z :setlocal foldexpr=(getline(v:lnum)=~@/)?0:(getline(v:lnum-1)=~@/)\|\|(getline(v:lnum+1)=~@/)?1:2 foldmethod=expr foldlevel=0 foldcolumn=2&lt;CR&gt;
+
nnoremap \z :setlocal foldexpr=(getline(v:lnum)=~@/)?0:(getline(v:lnum-1)=~@/)\\|\\|(getline(v:lnum+1)=~@/)?1:2 foldmethod=expr foldlevel=0 foldcolumn=2<CR>
 
</pre>
 
</pre>
   
*The first line is an extension of <pre>foldexpr=(getline(v:lnum)=~@/)?0:1</pre>
+
*The <code>foldexpr</code> is an extension of <code>foldexpr=(getline(v:lnum)=~@/)?0:1</code>
*The second line (re)sets the foldmethod to expr(ession) plus.
+
*The following options set the <code>foldmethod</code> to use the fold expression, and some other convenient values.
   
 
First search for a pattern, then fold everything else with <code>\z</code><br />
To add a second level of context, you could add <pre>(getline(v:lnum-2)=~@/)\|\|(getline(v:lnum+2)=~@/)?2:3</pre> but it will take longer as folded lines (the majority) evaluate the full expression.
 
 
Use <code>zr</code> to display more context, or <code>zm</code> to display less context.
   
  +
===Using a user-defined command===
First search for /regexp/, then fold everything else with \z
 
 
If you want to search and fold with a single command, either add the following as well:
Use zr to display more context, use zm to display less context.
 
  +
<pre>
 
command! -nargs=+ Foldsearch exe "normal /".<q-args>."^M\z"
  +
</pre>
  +
 
Or get rid of the <code>\z</code> entirely:
   
If no context is desired, you can do the following instead:
 
 
<pre>
 
<pre>
set foldexpr=(getline(v:lnum)=~@/)?0:(getline(v:lnum)=~@/)\|\|(getline(v:lnum)=~@/)?0:1
+
command! -nargs=+ Foldsearch exe "normal /".<q-args>."^M" | setlocal foldexpr=(getline(v:lnum)=~@/)?0:(getline(v:lnum-1)=~@/)\|\|(getline(v:lnum+1)=~@/)?1:2 foldmethod=expr foldlevel=0 foldcolumn=2
map \z :set foldmethod=expr foldlevel=0 foldcolumn=1&lt;CR&gt;
 
 
</pre>
 
</pre>
   
 
In these last two code segments, be sure to replace the "^M" with an actual CTRL-M (carriage return) character. Type :Folds[earch] <search string>[ENTER] to use this command. Use zr to display more context, use zm to display less context as before.
If you want to search and fold with a single command, either add the following as well:
 
   
  +
===Customizing===
  +
To add a second level of context, you could add this to the end of foldexpr:
  +
<pre>(getline(v:lnum-2)=~@/)\|\|(getline(v:lnum+2)=~@/)?2:3</pre>
 
but it will take longer as folded lines (the majority) evaluate the full expression.
  +
 
If no context is desired at all, you can do the following instead:
 
<pre>
 
<pre>
 
foldexpr=(getline(v:lnum)=~@/)?0:(getline(v:lnum)=~@/)\|\|(getline(v:lnum)=~@/)?0:1
command! -nargs=+ Foldsearch exe "normal /".<q-args>."^M\z"
 
 
</pre>
 
</pre>
   
  +
==Search method==
Or get rid of the \z entirely:
 
  +
The line-based method has a major drawback: matches that depend on surround lines for lookahead or lookbehind will not match. To solve that, use real searching in the foldexpr, inside a function, as follows:
   
  +
<source lang='vim'>
  +
" command to fold everything except what you searched for
  +
command! -nargs=* Foldsearch
  +
\ if <q-args> != '' |
  +
\ exe "normal /".<q-args>."\<CR>" |
  +
\ endif |
  +
\ if @/ != '' |
  +
\ setlocal
  +
\ foldexpr=FoldRegex(v:lnum,@/,2)
  +
\ foldmethod=expr
  +
\ foldlevel=0 |
  +
\ endif
  +
  +
function! FoldRegex(lnum,pat,context)
  +
" get start/end positions for context lines
  +
let startline=a:lnum-a:context
  +
while startline < 1
  +
let startline+=1
  +
endwhile
  +
let endline=a:lnum+a:context
  +
while endline > line('$')
  +
let endline-=1
  +
endwhile
  +
  +
let returnval = 2
  +
  +
let pos=getpos('.')
  +
  +
" search from current line to get matches ON the line
  +
call cursor(a:lnum, 1)
  +
let matchline=search(a:pat,'cW',endline)
  +
if matchline==a:lnum
  +
let returnval = 0
  +
elseif matchline > 0
  +
" if current line didn't match, there could have been a match within
  +
" trailing context lines
  +
let returnval = 1
  +
else
  +
" if no match at current line, search leading context lines for a match
  +
call cursor(startline, 1)
  +
let matchline=search(a:pat,'cW',a:lnum)
  +
if matchline > 0
  +
let returnval = 1
  +
endif
  +
endif
  +
  +
call setpos('.',pos)
  +
  +
return returnval
  +
endfun
  +
</source>
  +
  +
==Uses==
  +
Aside from the possible convenience of seeing only the search terms and their immediate context, this method of folding can be used for practical purposes as well. For example, viewing a "quick and dirty" api of a source code file.
  +
  +
To make a command to do a quick Java API for example, use:
 
<pre>
 
<pre>
  +
" View the methods and variables in a java source file."
command! -nargs=+ Foldsearch exe "normal /".<q-args>."^M" | setlocal foldexpr=(getline(v:lnum)=~@/)?0:(getline(v:lnum-1)=~@/)\|\|(getline(v:lnum+1)=~@/)?1:2 foldmethod=expr foldlevel=0 foldcolumn=2
 
  +
command! Japi Foldsearch public\s\|protected\s\|private\s
 
</pre>
 
</pre>
   
  +
Note that a better way to do this for languages that support it would be to use syntax highlighting. [[Check your syntax files for configurable options|Check the syntax file]] for the language in question to determine if this is an option. Many syntax files such as those for C, Perl, and [[Syntax folding of Vim scripts|VimL]] all define rules for at least some syntax-based folding, using <code>:set foldmethod=syntax</code>. Other languages such as Java do not currently have this functionality built in, so keep this idea around just in case you need it!
In these last two code segments, be sure to replace the "^M" with an actual CTRL-M (carriage return) character. Type :Folds[earch] &lt;search string&gt;[ENTER] to use this command.
 
   
  +
==Related plugins==
This tip combines well with [[VimTip108]] (space bar in normal mode toggles a fold).
 
  +
*{{script|id=158}}
  +
*{{script|id=318}}
  +
*{{script|id=2521}}
  +
*{{script|id=2302}}
   
  +
==See also==
This script does the same thing and more: {{script|id=158}}
 
  +
*[[VimTip108|Toggle a fold with a single keystroke]] press space bar in normal mode to toggle fold
  +
*[[VimTip213|Delete all lines containing a pattern]] to delete matching or non-matching lines, rather than folding them
   
== Comments ==
+
==Comments==
  +
{{Todo}}
'''TODO:''' Explain better what the foldexpr string does.
+
*Explain better how the foldexpr string works.
----
 
  +
*:Convert it into a function?
  +
*::An excellent idea, or use line continuations. The current state is very hard to read (especially in IE where the scroll bar obscures part of the text).
  +
*Check if the comments in the original tip should be added (have they been taken into account?).
  +
*:Yes, they have, though the link to the script should probably be moved to a more prominent place.
  +
*A quick test suggests that "\<CR>" can be used instead of an actual ^M (so we should probably edit the tip in due course, although "\<CR>" possibly relies on some non-compatibility setting).
  +
*:I think if people are using a crazy setting that breaks things like "\<CR>" they should figure out how to fix the consequences on their own. No harm in mentioning it though, I guess.
  +
*Link to [[VimTip76|Folding for Quickfix]].
  +
*Perhaps add following section (merged in from [[VimTip1022]]). A very quick test suggests it's ok.
  +
*:I like it. Probably add it as part of the explanation, though. Maybe build up to the final expression gradually.
   
  +
==Simple folding==
[[Category:Folding]]
 
  +
After executing the following, you can search for a pattern then press F8 to fold misses.
[[Category:Searching]]
 
  +
<pre>
  +
:set foldexpr=getline(v:lnum)!~@/
  +
:nnoremap <F8> :set foldmethod=expr<CR><Bar>zM
  +
</pre>

Revision as of 17:44, 30 September 2014

Tip 282 Printable Monobook Previous Next

created 2002 · complexity basic · author Chris Butler · version 6.0


When searching with /, it would sometimes be nice to fold everything except for matches to your search. The following code does this, providing two levels of folding to allow you to show some context around each search match as well.

Line-based method

Using a '/' search and key mapping

Add the following to your vimrc to provide a mapping to fold on an already-performed search:

nnoremap \z :setlocal foldexpr=(getline(v:lnum)=~@/)?0:(getline(v:lnum-1)=~@/)\\|\\|(getline(v:lnum+1)=~@/)?1:2 foldmethod=expr foldlevel=0 foldcolumn=2<CR>
  • The foldexpr is an extension of foldexpr=(getline(v:lnum)=~@/)?0:1
  • The following options set the foldmethod to use the fold expression, and some other convenient values.

First search for a pattern, then fold everything else with \z
Use zr to display more context, or zm to display less context.

Using a user-defined command

If you want to search and fold with a single command, either add the following as well:

command! -nargs=+ Foldsearch exe "normal /".<q-args>."^M\z"

Or get rid of the \z entirely:

command! -nargs=+ Foldsearch exe "normal /".<q-args>."^M" | setlocal foldexpr=(getline(v:lnum)=~@/)?0:(getline(v:lnum-1)=~@/)\|\|(getline(v:lnum+1)=~@/)?1:2 foldmethod=expr foldlevel=0 foldcolumn=2

In these last two code segments, be sure to replace the "^M" with an actual CTRL-M (carriage return) character. Type :Folds[earch] <search string>[ENTER] to use this command. Use zr to display more context, use zm to display less context as before.

Customizing

To add a second level of context, you could add this to the end of foldexpr:

(getline(v:lnum-2)=~@/)\|\|(getline(v:lnum+2)=~@/)?2:3

but it will take longer as folded lines (the majority) evaluate the full expression.

If no context is desired at all, you can do the following instead:

foldexpr=(getline(v:lnum)=~@/)?0:(getline(v:lnum)=~@/)\|\|(getline(v:lnum)=~@/)?0:1

Search method

The line-based method has a major drawback: matches that depend on surround lines for lookahead or lookbehind will not match. To solve that, use real searching in the foldexpr, inside a function, as follows:

" command to fold everything except what you searched for
command! -nargs=* Foldsearch
      \ if <q-args> != '' |
      \   exe "normal /".<q-args>."\<CR>" |
      \ endif |
      \ if @/ != '' |
      \   setlocal
      \     foldexpr=FoldRegex(v:lnum,@/,2)
      \     foldmethod=expr
      \     foldlevel=0 |
      \ endif

function! FoldRegex(lnum,pat,context)
  " get start/end positions for context lines
  let startline=a:lnum-a:context
  while startline < 1
    let startline+=1
  endwhile
  let endline=a:lnum+a:context
  while endline > line('$')
    let endline-=1
  endwhile

  let returnval = 2

  let pos=getpos('.')

  " search from current line to get matches ON the line
  call cursor(a:lnum, 1)
  let matchline=search(a:pat,'cW',endline)
  if matchline==a:lnum
    let returnval = 0
  elseif matchline > 0
    " if current line didn't match, there could have been a match within
    " trailing context lines
    let returnval = 1
  else
    " if no match at current line, search leading context lines for a match
    call cursor(startline, 1)
    let matchline=search(a:pat,'cW',a:lnum)
    if matchline > 0
      let returnval = 1
    endif
  endif

  call setpos('.',pos)

  return returnval
endfun

Uses

Aside from the possible convenience of seeing only the search terms and their immediate context, this method of folding can be used for practical purposes as well. For example, viewing a "quick and dirty" api of a source code file.

To make a command to do a quick Java API for example, use:

" View the methods and variables in a java source file."
command! Japi Foldsearch public\s\|protected\s\|private\s

Note that a better way to do this for languages that support it would be to use syntax highlighting. Check the syntax file for the language in question to determine if this is an option. Many syntax files such as those for C, Perl, and VimL all define rules for at least some syntax-based folding, using :set foldmethod=syntax. Other languages such as Java do not currently have this functionality built in, so keep this idea around just in case you need it!

Related plugins

See also

Comments

 TO DO 

  • Explain better how the foldexpr string works.
    Convert it into a function?
    An excellent idea, or use line continuations. The current state is very hard to read (especially in IE where the scroll bar obscures part of the text).
  • Check if the comments in the original tip should be added (have they been taken into account?).
    Yes, they have, though the link to the script should probably be moved to a more prominent place.
  • A quick test suggests that "\<CR>" can be used instead of an actual ^M (so we should probably edit the tip in due course, although "\<CR>" possibly relies on some non-compatibility setting).
    I think if people are using a crazy setting that breaks things like "\<CR>" they should figure out how to fix the consequences on their own. No harm in mentioning it though, I guess.
  • Link to Folding for Quickfix.
  • Perhaps add following section (merged in from VimTip1022). A very quick test suggests it's ok.
    I like it. Probably add it as part of the explanation, though. Maybe build up to the final expression gradually.

Simple folding

After executing the following, you can search for a pattern then press F8 to fold misses.

:set foldexpr=getline(v:lnum)!~@/
:nnoremap <F8> :set foldmethod=expr<CR><Bar>zM