Vim's Fold Expression Does Not Using my Function - vim

I am trying to use expr fold in vim:
I create a file in ~/.vim/ftplugin/python/folding.vim
and its' content:
setlocal foldmethod=expr
setlocal foldexpr=GetFold(v:lnum)
function! GetFold(lnum)
if a:lnum > 10
return 1
endif
return 0
endfunctio
this 'GetFold' function is a simple version for test
however when I open a python file, the fold does not work, no folding occurred,
while I expect the lines over 10 should fold
the python file content:
import numpy
import numpy as np
def tm(a):
a += 1
print(a)
class _A_3(object):
def __init__(self):
x = 1
y = 1
z = 1
def update(self):
x = 2
y = 2
z = 2
if __name__ == '__main__':
arr = np.array([1,2,3],dtype=np.float64)
print(str(type(arr)))
tm(0)
ta = A()
I checked the filetype, :set filetype? it shows filetype=python, so filetype no problem.
I checked the foldmethod, :set foldmethod? it shows foldmethod=expr, so foldmethod no problem.
I checked if the 'GetFold' function exists and works correctly:
:echom GetFold(5) it shows 0, :echom GetFold(12) it shows 1, so 'GetFold' function seems ok.
I use vim's 'foldlevel()' function to check:
:echom foldlevel(5) it shows 0, :echom foldlevel(11) it shows 0,
:echom foldlevel(15) it shows 0 also.
So, it seems vim does not set the foldlevel by my function, I can't figure out where is the problem. I checked the :help fold-expr, but I think follows the instruction as it says:
my vim version infomation:
change 'GetFold' return from 1,0 to '1','0' and line 11's return to '>1' dose not help.

Related

Highlight entire line according to pattern match

I'm trying to get to vim or nvim to highlight the entire gui line if there is a match on the screen.
I have no idea how to begin approaching this or what to search for.
I'm able to get vim to highlght according to a pattern match, but I want it to highlight the entire gui width like it does at the bottom of the screen (in black) as shown above.
How can I achieve this?
As far as I know, it is not possible to highlight the entire line except sign feature. The following example uses #/ register value to find the last searched lines and will show the Search highlight on them.
function! s:ToggleHighlightSearchLines()
if !exists('s:id')
let s:id = localtime()
let lc = [line('.'), col('.')]
call cursor([1, 1])
let s:sc = &signcolumn
let &signcolumn = 'no'
sign define hlsLines linehl=Search
let s:sp = 0
while 1
let ln = search(#/, 'W')
if ln == 0 | break | endif
execute 'sign place ' . s:id . ' line=' . ln . ' name=hlsLines'
let s:sp += 1
endwhile
call cursor(lc)
else
while 0 < s:sp
execute 'sign unplace ' . s:id
let s:sp -= 1
endwhile
sign undefine hlsLines
let &signcolumn = s:sc
unlet s:id s:sp s:sc
endif
endfunction
command! ToggleHLSLines call s:ToggleHighlightSearchLines()

Table of contents with Fold in Vim

How can I unfold only the folds containing a fold, to get an outline of my document?
If everything is folded and I press zr a few times I get something close to what I want, except that if parts have different depths I'm either not seeing some folds or seeing some content.
In this example:
# Title {{{1
# Subtitle {{{2
some code here
# Another Title {{{1
code here directly under the level 1 title
I would like to see this when folded:
# Title {{{1
# Subtitle {{{2
# Another Title {{{1
That's not trivial; I've solved this with a recursive function that determines the level of nesting, and then closes the innermost folds.
" [count]zy Unfold all folds containing a fold / containing at least
" [count] levels of folds. Like |zr|, but counting from
" the inside-out. Useful to obtain an outline of the Vim
" buffer that shows the overall structure while hiding the
" details.
function! s:FoldOutlineRecurse( count, startLnum, endLnum )
silent! keepjumps normal! zozj
if line('.') > a:endLnum
" We've moved out of the current parent fold.
" Thus, there are no contained folds, and this one should be closed.
execute a:startLnum . 'foldclose'
return [0, 1]
elseif line('.') == a:startLnum && foldclosed('.') == -1
" We've arrived at the last fold in the buffer.
execute a:startLnum . 'foldclose'
return [1, 1]
else
let l:nestLevelMax = 0
let l:isDone = 0
while ! l:isDone && line('.') <= a:endLnum
let l:endOfFold = foldclosedend('.')
let l:endOfFold = (l:endOfFold == -1 ? line('$') : l:endOfFold)
let [l:isDone, l:nestLevel] = s:FoldOutlineRecurse(a:count, line('.'), l:endOfFold)
if l:nestLevel > l:nestLevelMax
let l:nestLevelMax = l:nestLevel
endif
endwhile
if l:nestLevelMax < a:count
execute a:startLnum . 'foldclose'
endif
return [l:isDone, l:nestLevelMax + 1]
endif
endfunction
function! s:FoldOutline( count )
let l:save_view = winsaveview()
try
call cursor(1, 0)
keepjumps normal! zM
call s:FoldOutlineRecurse(a:count, 1, line('$'))
catch /^Vim\%((\a\+)\)\=:E490:/ " E490: No fold found
" Ignore, like zr, zm, ...
finally
call winrestview(l:save_view)
endtry
endfunction
nnoremap <silent> zy :<C-u>call <SID>FoldOutline(v:count1)<CR>

How can I create useful fold expressions if they can't have any side effects?

I'm trying to make a very simple fold expression for Ruby:
let s:fold_indent = []
function! RubyFoldLevel(lnum)
let l:prevline = getline(a:lnum - 1)
let l:curline = getline(a:lnum)
let l:nextline = getline(a:lnum + 1)
" if l:curline=~'^\s*\(module\)\|\(class\)\|\(def\)\s'
if l:curline=~'^\s*def\s'
add(s:fold_indent, indent(a:lnum))
echo s:fold_indent
return 'a1'
elseif l:curline=~'^\s*end\s*$'
\ && len(s:fold_indent) > 0
\ && indent(a:lnum) == s:fold_indent[-1]
unlet s:fold_indent[-1]
echo s:fold_indent
return 's1'
end
return '='
endfunction
My plan is to add to the fold level ("a1") whenever I stumble upon a def, then subtract from the fold level ("s1") when I find the end at the same level of indentation. Here I'm attempting to do this by saving the indentation in a stack and only ending a fold when the topmost item in the stack matches the indentation level of the current line.
This doesn't work because the fold expression can't actually edit the contents of s:fold_indent (notice the echo s:fold_indent. It prints [] every time.) I suppose this makes sense since it's useful to be able to use the expression on any line regardless of order, however I can't work out how to write a useful fold expression without it.
Is it possible to fulfill my plan without maintaining a stack outside of the function?
Side effects in a fold expression only refer to changing text or switching windows (called textlock in Vim). The problem is rather that you've missed a :call in front of the add():
call add(s:fold_indent, indent(a:lnum))
(This becomes obvious when you manually invoke the function.)
Nonetheless, using shared state is problematic, because you have no control where (and in which order) Vim evaluates the fold expression. For Ruby folding, I'd rather rely on the syntax highlighting (which already defines regions for def ... end) to provide the folding information (and the syntax/ruby.vim that ships with Vim already does).
Alright, I think I figured it out. My function now indents any time module, class or def is encountered. Any time end is encountered, the function iterates backwards over previous lines until a module, class or def is found at the same indentation level as the end, and if one is found it subtracts the fold level.
After a few minutes of testing and a few lines of code to handle special cases (0 or 1 line folds) it appears to work perfectly:
function! RubyFoldLevel(lnum)
let l:prevline = getline(a:lnum - 1)
let l:curline = getline(a:lnum)
let l:nextline = getline(a:lnum + 1)
let l:fold_starter = '^\s*\(\(module\)\|\(class\)\|\(def\)\)\s'
let l:fold_ender = '^\s*end\s*$'
" Prevent 1-line folds.
if indent(a:lnum - 1) == indent(a:lnum + 1)
\ && l:prevline =~ l:fold_starter
\ && l:nextline =~ l:fold_ender
return "="
end
if l:prevline=~l:fold_starter
\ && !(l:curline =~ l:fold_ender && indent(a:lnum - 1) == indent(a:lnum))
return 'a1'
end
if l:nextline=~l:fold_ender
\ && !(l:curline =~ l:fold_starter && indent(a:lnum) == indent(a:lnum + 1))
let l:cursor = a:lnum + 1
while 1
let l:cursor = prevnonblank(l:cursor - 1)
" Fold starter found at this indentation level.
if indent(l:cursor) == indent(a:lnum + 1)
\ && getline(l:cursor)=~l:fold_starter
return 's1'
end
" No fold starter found at this indentation level.
if indent(l:cursor) < indent(a:lnum + 1)
break
end
endwhile
end
return '='
endfunction
setlocal foldmethod=expr
setlocal foldexpr=RubyFoldLevel(v:lnum)
Needless to say, this fold expression will only work on source files with "correct" indentation.

Fold up to the fold start, instead of the indent start

I use foldmethod=indent and when I fold code like this:
def cake():
#cake!
print( "cake" )
print( "for" )
print( "you" )
I see
def cake():
#cake!
print( "cake" ) +++ 3 lines folded
but I want to see
def cake(): +++ 5 lines folded
Is there a way to do fold up to the first line (def cake():) like this?
Chapters 48 and 49 of Learn Vimscript the Hard Way talk about how to do that, using foldmethod=expr instead of indent. Basically you need to make a custom ftplugin and put a folding script in it; the script contains functions used to determine what fold level different lines should have.
As luck would have it, the example code given in those two chapters is for the Potion language which, like Python, is whitespace-sensitive, so it should be pretty easy to adapt it to Python. Since Vim already comes with a Python ftplugin, I think you can put the folding script described on the site into .vim/after/ftplugin/python instead of .vim/ftplugin/potion.
I solved this using this tutorial.
This is the finished bunch of functions:
fu! Indent_level(lnum)
return indent(a:lnum) / &shiftwidth
endfunction
fu! Next_non_blank_line(lnum)
let numlines = line('$')
let current = a:lnum + 1
while current <= numlines
if getline(current) =~? '\v\S'
return current
endif
let current += 1
endwhile
return -2
endfunction
fu! Custom_fold_expr(lnum)
if getline(a:lnum) =~? '\v^\s*$'
return '-1'
endif
let this_indent = Indent_level(a:lnum)
let next_indent = Indent_level(Next_non_blank_line(a:lnum))
if next_indent == this_indent
return this_indent
elseif next_indent < this_indent
return this_indent
elseif next_indent > this_indent
return '>' . next_indent
endif
endf
set foldexpr=Custom_fold_expr(v:lnum)
foldmethod=expr
Please don't edit the indentation of the "end" markers on this post, it looks gorgeous after you put this in your vimrc.

How to resize a window to fit, taking into account only logical lines?

I'm looking to write a function that I can call from a map. The idea is to resize a window to fit the buffer contents. This isn't too difficult:
fu! ResizeWindow(vert) "{{{
if a:vert
let longest = max(map(range(1, line('$')), "virtcol([v:val, '$'])"))
exec "vertical resize " . (longest+4)
else
exec 'resize ' . line('$')
1
endif
endfu "}}}
I would, however, like the function to take logical lines into account when calculating the height (I'm not overly worried about width).
For example, a line that has wrapped (due to :set wrap) would count as two or more lines. A block of 37 lines that are folded would only count as one.
Does anyone know of a convenient way of getting this 'logical line count' without having to try and calculate it manually? If I do need to do this manually are there any other cases I'm missing that would cause a line to be represented with a potentially different number of lines?
For anyone interested, I gave up trying to find a simple solution to this. Below is the code I ended up with. It takes into account a couple of obvious edge cases; I'm sure others remain. Suggestions for improvement are very welcome.
fu! Sum(vals) "{{{
let acc = 0
for val in a:vals
let acc += val
endfor
return acc
endfu "}}}
fu! LogicalLineCounts() "{{{
if &wrap
let width = winwidth(0)
let line_counts = map(range(1, line('$')), "foldclosed(v:val)==v:val?1:(virtcol([v:val, '$'])/width)+1")
else
let line_counts = [line('$')]
endif
return line_counts
endfu "}}}
fu! LinesHiddenByFoldsCount() "{{{
let lines = range(1, line('$'))
call filter(lines, "foldclosed(v:val) > 0 && foldclosed(v:val) != v:val")
return len(lines)
endfu "}}}
fu! AutoResizeWindow(vert) "{{{
if a:vert
let longest = max(map(range(1, line('$')), "virtcol([v:val, '$'])"))
exec "vertical resize " . (longest+4)
else
let line_counts = LogicalLineCounts()
let folded_lines = LinesHiddenByFoldsCount()
let lines = Sum(line_counts) - folded_lines
exec 'resize ' . lines
1
endif
endfu "}}}

Resources