Indenting fold text - vim

When you unfold nested levels of your code the folded text in nested code is not indented. It begins on the beginning of the line with + instead of starting indented.
Do you know how to change it?

If you want the fold text to be indented at the same level as the first line of the fold, you need to prepend the indent level to the foldtext:
function! MyFoldText()
let indent_level = indent(v:foldstart)
let indent = repeat(' ',indent_level)
...
...
return indent . txt
endfunction
Here I am assuming that the string txt is your existing foldtext, so all you need to do is add it to the end of indent.
But I am not sure if that is what you want to achieve.
EDIT:
Now I have seen your picture, I'm not sure if this is what you want. You could try stripping the leading whitespace before appending to the +. So the foldtext you want will be something like indent . '+' . txt.
Maybe.

Aha
You might want to comment out this function in your .vimrc:
set foldtext=MyFoldText()
set fillchars=fold:_
This is what makes your fold text appearing non default, by using the function:
function! MyFoldText()
" setting fold text
let nl = v:foldend - v:foldstart + 1
let comment = substitute(getline(v:foldstart),"^ *\" *","",1)
let linetext = substitute(getline(v:foldstart+1),"^ *","",1)
let txt = '+ ' . comment . ': ' . nl . ' ' . v:foldstart . ' '
return txt
endfunction
As it happens, I quite like that function, but of course, de gustibus...

Related

centering and filling multiple space with # in VIM

I want to make some nice separation between different parts of the file in Vim :
I want to fill a line by #'s and then write in the center my title :
############################## Centered Title ################################
So, for now (since by default the terminal is 80 characters wide), I do
i (insert mode)
#
esc
79 .
Which makes a line of #'s.
Then I have to count the width of my title, compute the starting point, go to the computed starting point and replace with R.
It is a bit tedious ... In the other hand, I know to center a text in VIM by using :center in visual mode.
Is it possible to combine both to have more directly what I am looking for ?
As answers in linked thread does not contain explained vimscript solution I would share the one I wrote at that moment.
function Foo()
let title = input('Title: ')
put =title
center
let line=getline(line('.'))
let spaces = matchstr(line, '^\s*\ze\s')
let prefix = substitute(spaces, ' ', '#', 'g')
call setline(line('.'), prefix . ' ' . trim(line) . ' ')
normal 80A#
normal d80|
endfunction
noremap <leader>x :call Foo()<cr>
let title = input('Title: ') -> Function that ask for title and store it in var
put =title -> paste content to current line
center -> center it to fill beginning with spaces
let line=getline(line('.')) -> get content of current line into variable
let spaces = matchstr(line, '^\s*\ze\s') -> stores all whitespaces expect last one into variable
let prefix = substitute(spaces, ' ', '#', 'g') -> converts spaces to #
call setline(line('.'), prefix . ' ' . trim(line) . ' ') -> changes content of current line
normal 80A# -> append many # at the end of line
normal d80| -> delete everything after 80 column

Placing cursor after lines appended via append / line

I use the following function to insert a break in comments of the following format:
Break:
# Notes -------------------------------------------------------------------
Function:
" Insert RStudio like section break
function! InsertSectionBreak()
let title = input("Section title: ") " Collect title
let title_length = strlen(title) " Number of repetitions
let times = 80 - (title_length + 1)
let char = "-" " Create line break
let sep_line = repeat(char, times)
let final_string = '\n#' . ' ' . title . ' ' . sep_line " Create final title string
call cursor( line('.')+1, 1)
call append(line('.')+1, final_string) " Get current line and insert string
endfunction
" Map function to keyboard shortcut ';s'
nmap <silent> ;s :call InsertSectionBreak()<CR>
Problem
After performing the operation I would like to place the cursor one line below the created section.
Desired behaviour:
# Notes -------------------------------------------------------------------
<cursor should land here>
Current behaviour
The cursors stays on the current line.
<some code I'm typing when I exit insert mode and call ;s - cursor stays here>
# Notes -------------------------------------------------------------------
<cursor lands here>
As a low-level function, append() is not affected by and also does not affect the cursor position. Therefore, you just need to adapt the cursor() arguments. I would also recommend to only change the cursor at the very end, to make the calculation based on line('.') easier:
function! InsertSectionBreak()
let title = input("Section title: ") " Collect title
let title_length = strlen(title) " Number of repetitions
let times = 80 - (title_length + 1)
let char = "-" " Create line break
let sep_line = repeat(char, times)
let final_string = '#' . ' ' . title . ' ' . sep_line " Create final title string
call append(line('.')+1, ['', final_string]) " Get current line and insert string
call cursor(line('.')+4, 1)
endfunction
Additional notes
The '\n#' string includes a literal \n, not a newline character. For that, double quotes would have to be used. However, even that won't work with append() because it always inserts as one text line, rendering the newline as ^#. To include a leading empty line, pass a List of lines instead, and make the first list element an empty string.
You're using mostly low-level functions (cursor(), append()); you could have used higher-level functions (:normal! jj or :execute lnum for cursor positioning, :put =final_string for adding lines) instead. There would be more side effects (like adding to the jumplist, :put depending on and positioning the cursor already, and having the change marks delimit the added text); usually this is good (but it depends on the use case).
Mappings that interactively query stuff from the user are not very Vim-like. I would have rather defined a custom command (e.g. :Break {title}) that takes the title as an argument, and maybe an additional (incomplete command-line) mapping for quick access :nnoremap ;s :Break<Space>. This way, you could easily repeat the last insertion with the same title via #:, for instance.

In vim, how to split a word and flip the halves? FooBar => BarFoo

I sometimes write a multi-word identifier in one order, then decide the other order makes more sense. Sometimes there is a separator character, sometimes there is case boundary, and sometimes the separation is positional. For example:
$foobar becomes $barfoo
$FooBar becomes $BarFoo
$foo_bar becomes $bar_foo
How would I accomplish this in vim? I want to put my cursor on the word, hit a key combo that cuts the first half, then appends it to the end of the current word. Something like cw, but also yanking into the cut buffer and then appending to the current word (eg ea).
Nothing general and obvious comes to mind. This is more a novelty question than one of daily practical use, but preference is given to shortest answer with fewest plugins. (Hmm, like code golf for vim.)
You can use this function, it swaps any word of the form FooBar, foo_bar, or fooBar:
function! SwapWord()
" Swap the word under the cursor, ex:
" 'foo_bar' --> 'bar_foo',
" 'FooBar' --> 'BarFoo',
" 'fooBar' --> 'barFoo' (keeps case style)
let save_cursor = getcurpos()
let word = expand("<cword>")
let match_ = match(word, '_')
if match_ != -1
let repl = strpart(word, match_ + 1) . '_' . strpart(word, 0, match_)
else
let matchU = match(word, '\u', 1)
if matchU != -1
let was_lower = (match(word, '^\l') != -1)
if was_lower
let word = substitute(word, '^.', '\U\0', '')
endif
let repl = strpart(word, matchU) . strpart(word, 0, matchU)
if was_lower
let repl = substitute(repl, '^.', '\L\0', '')
endif
else
return
endif
endif
silent exe "normal ciw\<c-r>=repl\<cr>"
call setpos('.', save_cursor)
endf
Mapping example:
noremap <silent> gs :call SwapWord()<cr>
Are you talking about a single instance, globally across a file, or generically?
I would tend to just do a global search and replace, e.g.:
:1,$:s/$foobar/$barfoo/g
(for all lines, change $foobar to $barfoo, every instance on each line)
EDIT (single occurrence with cursor on the 'f'):
3xep
3xep (had some ~ in there before the re-edit of the question)
4xea_[ESC]px
Best I got for now. :)
nnoremap <Leader>s dwbP
Using Leader, s should now work.
dw : cut until the end of the word from cursor position
b : move cursor at the beginning of the word
P : paste the previously cut part at the front
It won't work for you last example though, you have to add another mapping to deal with _ .
(If you don't know what Leader is, see :help mapleader)

Jump to the end of a long list of repeated pattern

I have a big file with a lot of lines that share the same pattern, something like this:
dbn.py:206 ... (some other text) <-- I am here
dbn.py:206 ... (some other text)
...
(something I don't know) <-- I want to jump here
Is there a quick way in Vim to jump to the place where the succession of dbp.py:206 ends?
/^\(dbn.py\)\#!
Matches first line which does not start with the text inside the escaped parentheses.
If you want quick access to this you could add a vmap which yanks the visually selected text and inserts it in the right spot (but first escaping it with escape(var, '/').
Try this vmap: vmap <leader>n "hy<Esc>/^\(<C-R>=escape(#h,'/')<CR>\)\#!<CR>
Press n when visually selecting the text you wish to skip and you should be placed on the next first line which does not begin with the selection.
I just write a function to select identical lines:
nnoremap vii :call SelectIdenticalLines()<CR>
fun! SelectIdenticalLines()
let s = getline('.')
let n = line('.')
let i = n
let j = n
while getline(i)==s && i>0
let i-=1
endwhile
while getline(j)==s && j<=line('$')
let j+=1
endwhile
call cursor(i+1, 0)
norm V
call cursor(j-1, 0)
endfun
type vii to select identical lines (feel free to change the key-binding)
type zf to fold them.
type za to toggle folding
It's handy when you want to squeeze several empty line.
It acts like C-x C-o in emacs.
One option is to go to the bottom of the file and search backwards for the last line you want, then go down one:
G ?^dbn\.py:206?+1

What is the easiest way to swap occurrences of two strings in Vim?

What is the easiest way to replace all occurrences of string_a with string_b while at the same time changing anything that was already string_b into string_a? My current method is as follows:
:s/string_a/string_c/g
:s/string_b/string_a/g
:s/string_c/string_b/g
Although this works, it requires extra typing and seems inefficient. Does anybody know of a better way to do this?
I'd do it like this:
:%s/\v(foo|bar)/\={'foo':'bar','bar':'foo'}[submatch(0)]/g
But that's too much typing, so I'd do this:
function! Mirror(dict)
for [key, value] in items(a:dict)
let a:dict[value] = key
endfor
return a:dict
endfunction
function! S(number)
return submatch(a:number)
endfunction
:%s/\v(foo|bar)/\=Mirror({'foo':'bar'})[S(0)]/g
But that still requires typing foo and bar twice, so I'd do something like this:
function! SwapWords(dict, ...)
let words = keys(a:dict) + values(a:dict)
let words = map(words, 'escape(v:val, "|")')
if(a:0 == 1)
let delimiter = a:1
else
let delimiter = '/'
endif
let pattern = '\v(' . join(words, '|') . ')'
exe '%s' . delimiter . pattern . delimiter
\ . '\=' . string(Mirror(a:dict)) . '[S(0)]'
\ . delimiter . 'g'
endfunction
:call SwapWords({'foo':'bar'})
If one of your words contains a /, you have to pass in a delimiter which you know none of your words contains, .e.g
:call SwapWords({'foo/bar':'foo/baz'}, '#')
This also has the benefit of being able to swap multiple pairs of words at once.
:call SwapWords({'foo':'bar', 'baz':'quux'})
You can do this easily with Tim Pope's Abolish plugin
:%S/{transmit,receive}/{receive,transmit}
Here is how I swap two words skip & limit:
%s/skip/xxxxx/g | %s/limit/skip/g | %s/xxxxx/limit/g
Pretty sure someone could turn it into a shorter command which accepts two arguments.
The swapstrings plugin provides a handy command for this:
:SwapStrings string_a string_b
You can do it with a single command as shown in my code below:
:%s/\<\(string_a\|string_b\)\>/\=strpart("string_bstring_a", 8 * ("string_b" == submatch(0)), 8)/g

Resources