centering and filling multiple space with # in VIM - 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

Related

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)

How do I substitute the word under cursor in vim script?

After getting the current word with expand("<cWORD>") and processing the result string, I'm trying to replace the current word with it.
How can I do this?
EDIT Source added. I wrote it in python.
cur_word = vim.eval('expand("<cWORD>")')
parts = cur_word.split('.')
if parts:
obj, accesses = parts[0], parts[1:]
result = obj + ''.join("['%s']"%a for a in accesses)
# how do I replace the current word with result?
Edit
It looks like you wanted this:
viW
:s/\%V\.\(\w\+\)\%V/\="['" . submatch(1) . "']"/g
E.g., for the following text, curosr on the second line:
x = a.get.property;
x = a.git.another.property; # cursor on the first letter 'e'
The result will be
x = a.get.property;
x = a['git']['another']['property'];
You probably wanted you
yank one word, then
move the cursor (which you don't mention)
_replace the word under cursor by previously yanked word?
That would be
yiW
(move cursor around)
viWp
So e.g.:
the lazy cow mooned over the racy hump
cursor here: ----> +
Now, doing yiW (yank inner WORD), Fa (back to:)
the lazy cow mooned over the racy hump
--> +
Now, viWp replaces current WORD:
the over cow mooned over the racy hump
--> +
In the python interface for Vim, you can execute normal mode command, in your case,
vim.command("normal BcW%s" % result)
will do the trick.

Indenting fold text

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...

VIM: How to avoid substitution within substitution?

I created a function to search a custom number of empty lines (with or without spaces) and to replace them with a new (custom) number of empty lines.
fun! s:AddRemoveNumbEmptyLines()
if !exists("emptylinesbefore")
let emptylinesbefore = "How many empty lines do you search? \n (p.e. 2,3 4, ,3)"
endif
let b = inputdialog(emptylinesbefore)
if !exists("emptylinesafter")
let emptylinesafter = "How many empty lines must it be?"
endif
let c = inputdialog(emptylinesafter)
let m = repeat('\r', c)
exe 's/\(^\s*$\n\)\{'.b.'}/'.m.'/gc'
endfun
Let say b = 2, (2 and more) AND m = 3
If vim finds 4 empty lines it does a substitution to 3 empty lines.
(thats ok).
But when I refuse the substitution request (I use the "c" (confirm) flag) it finds at the same place 3 empty lines and asks again if it has to be replaced with 3 empty lines. When I refuse again, it finds at the same place, 2 empty lines and asks again if I want to do a substitution.
How can I avoid these multiple substitution requests (at the same place)?
Hope I made myself clear :)
resolved it! I just had to check for a non space \S in the line before and the line after.
My new exe = 's/^.*\S\+.*\n\zs\(^\s*$\n\)\{'.b.'}\ze\s*\S\+/'.m.'/gc

Resources