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.
Related
At present i can search for text
/text
and then delete line using dd and if i don't want to delete i can go for next match with n.
But is there any more fast way to do that!
This command below deletes all the lines containing text, but the problem is that it deletes all lines at once, sometimes that text is in some line that is exception.
:g/text/d
But i want something simple like like
:%s/text/some_other_text/gc
because this gives the option to substitute or not to.
You don't need a global command for this. The substitute command in by itself will suffice by
adding a wildcard
and adding an end-of-line.
example
%s/.*text.*\n//gc
You can mix :help global and :help substitute:
:g/text/s/.*\n//c
This will ask for confirmation before deleting every line containing text:
I've tried to found a way to use global and :substitute, and that correctly handles matches on consecutive lines, and matches on the first line, but alas, I'm not inspired.
So, I'm back to my basics: I've implemented what I think is missing: :confirm global.
The result has been pushed in my library plugin.
How it works:
I prepare a stateful variable that remembers the previous user choice when it matters (always, or quit, or last).
I execute global on the pattern, and for each match I check what the user wishes to do.
I either use the don't-ask-again states
or I ask using the StatusLineNC highlight group with echo "\rmessage" + :redraw. This is a very old trick we used to do even before Vim 6 IIRC.
The related code is the following:
" Function: lh#ui#ask(message) {{{3
function! lh#ui#ask(message) abort
redraw! " clear the msg line
echohl StatusLineNC
echo "\r".a:message
echohl None
let key = nr2char(getchar())
return key
endfunction
" Function: lh#ui#confirm_command(command) {{{3
" states:
" - ask
" - ignore
" - always
function! s:check() dict abort
if self.state == 'ignore'
return
elseif self.state == 'always'
let shall_execute_command = 1
elseif self.state == 'ask'
try
let cleanup = lh#on#exit()
\.restore('&cursorline')
\.restore_highlight('CursorLine')
set cursorline
hi CursorLine cterm=NONE ctermbg=black ctermfg=white guibg=black guifg=white
let choice = lh#ui#ask(self.message)
if choice == 'q'
let self.state = 'ignore'
let shall_execute_command = 0
" TODO: find how not to blink
redraw! " clear the msg line
elseif choice == 'a'
let self.state = 'always'
let shall_execute_command = 1
" TODO: find how not to blink
redraw! " clear the msg line
elseif choice == 'y'
" leave state as 'ask'
let shall_execute_command = 1
elseif choice == 'n'
" leave state as 'ask'
let shall_execute_command = 0
elseif choice == 'l'
let shall_execute_command = 1
let self.state = 'ignore'
endif
finally
call cleanup.finalize()
endtry
endif
if shall_execute_command
execute self.command
endif
endfunction
function! s:getSID() abort
return eval(matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_getSID$'))
endfunction
let s:k_script_name = s:getSID()
function! lh#ui#make_confirm_command(command, message) abort
let res = lh#object#make_top_type(
\ { 'state': 'ask'
\ , 'command': a:command
\ , 'message': a:message . ' (y/n/a/q/l/^E/^Y)'
\ })
call lh#object#inject_methods(res, s:k_script_name, 'check')
return res
endfunction
" Function: lh#ui#global_confirm_command(pattern, command, message [, sep='/']) {{{3
" Exemple: to remove lines that match a pattern:
" > call lh#ui#global_confirm_command(pattern, 'd', 'delete line?')
function! lh#ui#global_confirm_command(pattern, command, message, ...) abort
let cmd = lh#ui#make_confirm_command(a:command, a:message)
let sep = get(a:, 1, '/')
exe 'g'.sep.a:pattern.sep.'call cmd.check()'
endfunction
" Function: lh#ui#_confirm_global(param) {{{3
function! lh#ui#_confirm_global(param) abort
let sep = a:param[0]
let parts = split(a:param, sep)
if len(parts) < 2
throw "Not enough arguments to `ConfirmGlobal`!"
endif
let cmd = join(parts[1:])
call lh#ui#global_confirm_command(parts[0], cmd, cmd . ' on line?', sep)
endfunction
command! -nargs=1 ConfirmGlobal call lh#ui#_confirm_global('<args>')
From here you could either type:
:call lh#ui#global_confirm_command(pattern, 'd', 'delete line?')
or :ConfirmGlobal/pattern/d which generates a less instructive prompt
The most efficient way is to combine :glboal and :norm
:g/test/norm dd
There are a lot of solutions around to change the case of the entire word, but what I want is change the case in a way like this:
From "What is the meaning of life? A new proposal" I want: "What is The Meaning of Life? A new Proposal" if that's too hard this: "What Is The Meaning of Life? A New Proposal" would be enough.
This is commonly called title case; there is a visual mode mapping solution from the Vim Tips Wiki
function! TwiddleCase(str)
if a:str ==# toupper(a:str)
let result = tolower(a:str)
elseif a:str ==# tolower(a:str)
let result = substitute(a:str,'\(\<\w\+\>\)', '\u\1', 'g')
else
let result = toupper(a:str)
endif
return result
endfunction
vnoremap ~ y:call setreg('', TwiddleCase(#"), getregtype(''))<CR>gv""Pgvl
Alternative
If you want to implement a more robust solution yourself, my TextTransform plugin can help with setting up x{motion}, xx and {Visual}x mappings, so you just need to write the actual transformation function.
Edit: Ah well, couldn't stop myself, here is an implementation that is able to handle exceptions:
if ! exists('g:TitleCase_ExceptionPattern')
" Source:
" http://grammar.yourdictionary.com/capitalization/rules-for-capitalization-in-titles.html
let g:TitleCase_ExceptionPattern = '^\%(amid\|a[nst]\?\|and\|are\|but\|by\|down\|for\|from\|i[ns]\|into\|like\|near\|new\|nor\|old\|o[fnr]\|off\|onto\|over\|past\|per\|plus\|than\|the\|to\|up\|upon\|via\|with\)$'
endif
function! TitleCase( text )
return substitute(a:text, '\(^\<\w\+\>\)\|\<\w\+\>', '\=s:TitleCase(submatch(0), ! empty(submatch(1)))', 'g')
endfunction
function! s:TitleCase( word, isException )
if ! a:isException && a:word =~# g:TitleCase_ExceptionPattern
return tolower(a:word)
endif
return substitute(a:word, '\w', '\u&', '')
endfunction
call TextTransform#MakeMappings('', '<Leader>s~', 'TitleCase')
And here's a variant that cycles through Title Case with exceptions → Title Case all (without exceptions) → lowercase:
function! subs#TitleCase#Do( text )
let l:wordExpr = '\(^\<\w\+\>\|\<\w\+\>$\)\|\<\w\+\>'
let l:text = substitute(a:text, l:wordExpr, '\=s:TitleCase(submatch(0), ! empty(submatch(1)))', 'g')
if l:text ==# a:text
let l:text = substitute(a:text, l:wordExpr, '\=s:TitleCase(submatch(0), 1)', 'g')
if l:text ==# a:text
let l:text = substitute(a:text, l:wordExpr, '\L&', 'g')
endif
endif
return l:text
endfunction
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>
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.
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 "}}}