I have a file in which, below is the content.
A
A
A
A
A
A
A
A
A
.
.
.
A
Piece of code...
A
A
A
.
.
A
I want something like this using folding in vim
A
Piece of code
A
2 folds are created by folding repeated lines.
This should happen automatically as I open the file. Is it possible by doing it in vimrc?
How about this :help fold-expr:
setlocal foldenable foldmethod=expr
let &l:foldtext = 'printf("+-%s %d times: %s", v:folddashes, (v:foldend - v:foldstart + 1), getline(v:foldstart))'
let &l:foldexpr = 'getline(v:lnum) ==# getline(v:lnum + 1) && v:lnum < line("$") ? 1 : (getline(v:lnum - 1) ==# getline(v:lnum) ? "<1" : 0)'
It should be possible by using setlocal foldmethod=expr where you can write your own function:
setlocal foldmethod=expr
setlocal foldexpr=CustomFold(v:lnum)
function! CustomFold(lnum)
if getline(a:lnum) == getline(a:lnum-1) || getline(a:lnum) == getline(a:lnum+1)
return '1'
endif
return '0'
endfunction
However this is untested and you wouldn't want to do it for all files. But it should point you in the right direction. It also would not 100% match your criteria, but once you have a specific problem, you can always ask again
Related
I've implemented a pretty simple fold expression for Markdown, but it doesn't work for some reason. I've inserted some echom messages into the FoldExpr function and I can see them in the messages and they are correct. So it seems to be applied, but no folds are there. Apart from the fact I set foldlevel to zero, zM also have no effect.
Does anyone see the failure?
ftplugin/markdown_fold.vim :
" Generate the folds text for the `foldtext` option.
" Simply use the first line (which should contain the header)
" and extend it by the number of lines in this section.
"
function! FoldText()
let l:title = getline(v:foldstart)
let l:line_count = (v:foldend - v:foldstart)
let l:line_text = l:line_count > 1 ? 'lines' : 'line'
let l:text = l:title . ' [' . l:line_count . ' ' . l:line_text . ']'
return l:text
endfunction
" Return the fold level for the `foldexpr` option.
" Checks if the current line is a header.
" The level is equal to the number of hashes of the header.
" All lines which are not a header have the same level as their predecessor.
"
function! FoldExpr()
let l:line = getline(v:lnum)
let l:count = len(matchstr(l:line, '^#\+'))
if l:count > 0
return '>0'
else
return '='
endif
endfunction
" Use custom fold expression and text.
setlocal foldmethod=expr
setlocal foldexpr=FoldExpr()
setlocal foldtext=FoldText()
" Fold everything per default.
setlocal foldlevel=0
setlocal foldminlines=0
That >0 doesn't make sense. Unfortunately, Vim accepts it without complaining. According to :help fold-expr, a fold value of 0 means the line is not in a fold. To start a first-level fold, return >1 instead.
So I'm trying to write a function that makes inserting semi colons a bit more pleasant:
inoremap <leader>; <esc>:call InsertSemiColin()<cr>
Basically it checks to see if I'm standing at the end of the current line, if so I auto-format the code, insert the semi-colon at the end and break down to the next line (carriage return)
fun! InsertSemiColin()
if (!IsEOL()) | exec "normal! a;" | return | endif
exec "normal! \<esc>:OmniSharpCodeFormat\<cr>A;\<cr>"
endf:
fun! IsEOL()
" col number == length of current line?
return col('.') == strlen(getline(line('.'))) " or just getline('.')
endf
Expectation:
Result:
To try it out on your own, you can remove the code-formatting and just do:
exec "normal! a;\<cr>"
My indentation settings:
set smartindent
set tabstop=4
set shiftwidth=4
filetype plugin indent on
The weird thing is that if I don't insert the carriage return from a function, it works as expected!
inoremap <leader>; ;<cr>
Why is this happening? and how can I get the result I'm expecting?
Very frustrating, any help would be appreciated!
I would avoid leaving and re-entering insert mode for this via :help :map-expr:
inoremap <expr> <leader>; ';' . (IsEOL() ? '<esc>:OmniSharpCodeFormat<cr>A<cr>' : '')
For this to work, you need to change the comparison in the IsEOL() function:
fun! IsEOL()
" col number == length of current line?
return col('.') > strlen(getline(line('.'))) " or just getline('.')
endf
This also fixes the indent problem.
I would like the normal-mode command tilde ~, in addition to changing the case of letters, to also be able to change the text == to != and != to ==.
I find that I do this quite often and I'd like a shortcut that still uses the tilde.
This is fairly simple to do in vimscript.
Add the following to your .vimrc or source this code from a different file.
" ----------------------
" Tilde switches ==/!=
" ----------------------
function! TildeSwitch()
" Gets the pair of characters under the cursor, before and behind.
let cur_pair = getline(".")[col(".") - 2 : col(".") - 1]
let next_pair = getline(".")[col(".") - 1 : col(".")]
if cur_pair == "=="
normal! "_ch!
normal! l
elseif next_pair == "=="
normal! r!
elseif cur_pair == "!="
normal! "_ch=
normal! l
elseif next_pair == "!="
normal! r=
else
" If == and != are not found, simply use the regular tilde.
normal! ~
endif
endfunction
nnoremap <silent> ~ :silent call TildeSwitch()<cr>
Toggling between two alternatives (like == and !=) is only a special case of toggling between multiple options. I'd advise against overloading the binary ~ command and instead use <C-A> / <C-X>. The SwapIt - Extensible keyword swapper plugin offers this and actually has a default option to toggle ==, !=, <=, etc.
Let me propose an alternative implementation of this extended ~ command:
nnoremap <silent> ~ :call SwitchNeq()<cr>~
function! SwitchNeq()
let [s, c] = [#/, getpos('.')]
s/[!=]\ze\%#=\|\%#[!=]\ze=/\='!='[submatch(0)=='!']/e
let #/ = s
call setpos('.', c)
endfunction
Assuming I have multiple files opened as buffers in Vim. The files have *.cpp, *.h and some are *.xml. I want to close all the XML files with :bd *.xml. However, Vim does not allow this (E93: More than one match...).
Is there any way to do this?
P.S. I know that :bd file1 file2 file3 works. So can I somehow evaluate *.xml to file1.xml file2.xml file3.xml?
You can use <C-a> to complete all matches. So if you type :bd *.xml and then hit <C-a>, vim will complete the command to :bd file1.xml file2.xml file3.xml.
:3,5bd[elete]
Will delete buffer range from 3 to 5 .
You also can use alternatively use:
:.,$-bd[elete] " to delete buffers from the current one to last but one
:%bd[elete] " to delete all buffers
You can use this.
:exe 'bd '. join(filter(map(copy(range(1, bufnr('$'))), 'bufname(v:val)'), 'v:val =~ "\.xml$"'), ' ')
It should be quite easy to add it to a command.
function! s:BDExt(ext)
let buffers = filter(range(1, bufnr('$')), 'buflisted(v:val) && bufname(v:val) =~ "\.'.a:ext.'$"')
if empty(buffers) |throw "no *.".a:ext." buffer" | endif
exe 'bd '.join(buffers, ' ')
endfunction
command! -nargs=1 BDExt :call s:BDExt(<f-args>)
Try the script below. The example is for "txt", change it as needed, e.g. to "xml".
Modified buffers are not deleted. Press \bd to delete the buffers.
map <Leader>bd :bufdo call <SID>DeleteBufferByExtension("txt")
function! <SID>DeleteBufferByExtension(strExt)
if (matchstr(bufname("%"), ".".a:strExt."$") == ".".a:strExt )
if (! &modified)
bd
endif
endif
endfunction
[Edit]
Same without :bufdo (as requested by Luc Hermitte, see comment below)
map <Leader>bd :call <SID>DeleteBufferByExtension("txt")
function! <SID>DeleteBufferByExtension(strExt)
let s:bufNr = bufnr("$")
while s:bufNr > 0
if buflisted(s:bufNr)
if (matchstr(bufname(s:bufNr), ".".a:strExt."$") == ".".a:strExt )
if getbufvar(s:bufNr, '&modified') == 0
execute "bd ".s:bufNr
endif
endif
endif
let s:bufNr = s:bufNr-1
endwhile
endfunction
I too had a need for this functionality all the time. This is the solution I have in my vimrc.
function! GetBufferList()
return filter(range(1,bufnr('$')), 'buflisted(v:val)')
endfunction
function! GetMatchingBuffers(pattern)
return filter(GetBufferList(), 'bufname(v:val) =~ a:pattern')
endfunction
function! WipeMatchingBuffers(pattern)
let l:matchList = GetMatchingBuffers(a:pattern)
let l:count = len(l:matchList)
if l:count < 1
echo 'No buffers found matching pattern ' . a:pattern
return
endif
if l:count == 1
let l:suffix = ''
else
let l:suffix = 's'
endif
exec 'bw ' . join(l:matchList, ' ')
echo 'Wiped ' . l:count . ' buffer' . l:suffix . '.'
endfunction
command! -nargs=1 BW call WipeMatchingBuffers('<args>')
Now, I can just do :BW regex (e.g. :BW \.cpp$ and wipe all matching buffers that have match that pattern in their pathname.
If you want to delete rather than wipe, you can of course replace exec 'bw ' . join(l:matchList, ' ') with exec 'bd ' . join(l:matchList, ' ')
TAB will only autocomplete one file for you as of Vim 7.4.282
use <c-a> to autocomplete all files.
You can just use:
bd filetype
then just use <c-a> to facilitate the completion of all open files of specified filetype.
for example, you have 1.xml, 2.xml, 3.xml, and 4.xml,
you can do:
bd xml
then press <c-a>
vim will autocomplete for you as follow:
bd 1.xml 2.xml 3.xml 4.xml
you can just press enter to complete the command.
if you have made changes in one of the files mentioned above, do remember to do:
bd! xml
Very simply: use the :bd[elete] command. For example, :bd[elete] buf#1 buf#5 buf#3 will delete the buffers 1, 3, and 5.
Is there any easy way to toggle "do/end" and "{}" in ruby in Vim?
(TextMate does this with ^{.)
You'd have to either use searchpair(), or to play with % (as long as matchit is installed, and as you are on begin/end), then mark the two positions, test whether it's text or brackets, and finally update the two lines.
nnoremap <buffer> <c-x>{ :call <sid>ToggleBeginOrBracket()<cr>
let s:k_be = [ 'begin', 'end' ]
function! s:ToggleBeginOrBracket()
let c = lh#position#char_at_mark('.')
if c =~ '[{}]'
" don't use matchit for {,}
exe 'normal! %s'.s:k_be[1-(c=='}')]."\<esc>``s".s:k_be[(c=='}')]."\<esc>"
else
let w = expand('<cword>')
if w == 'begin'
" use mathit
normal %
exe "normal! ciw}\<esc>``ciw{\<esc>"
elseif w == 'end'
" use mathit
normal %
exe "normal! ciw{\<esc>``ciw}\<esc>"
else
throw 'Cannot toggle block: cursor is not on {, }, begin, nor end'
endif
endif
endfunction
Where lh#position#char_at_mark() is defined here.
PS: this is definitively a SO question as it combines ruby context, and advanced vim scripting.
Check out this new plugin: https://github.com/jgdavey/vim-blockle.
30 chars pad
There is a splitjoin.vim plugin that does this nicely (gJ/gS mappings for splitting/joining).