Placing cursor after lines appended via append / line - vim

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.

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

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 to use Snip() function in VIM

How I am suppose to use the the snip() function contained in the imaps.vim plugin in the latex package described below:
Snip: puts a scissor string above and below block of text {{{
Description:
this puts a the string "--------%<---------" above and below the visually
selected block of lines. the length of the 'tearoff' string depends on the
maximum string length in the selected range. this is an aesthetically more
pleasing alternative instead of hard-coding a length.
function! <SID>Snip() range
let i = a:firstline
let maxlen = -2
" find out the maximum virtual length of each line.
while i <= a:lastline
exe i
let length = virtcol('$')
let maxlen = (length > maxlen ? length : maxlen)
let i = i + 1
endwhile
let maxlen = (maxlen > &tw && &tw != 0 ? &tw : maxlen)
let half = maxlen/2
exe a:lastline
" put a string below
exe "norm! o\<esc>".(half - 1)."a-\<esc>A%<\<esc>".(half - 1)."a-"
" and above. its necessary to put the string below the block of lines
" first because that way the first line number doesnt change...
exe a:firstline
exe "norm! O\<esc>".(half - 1)."a-\<esc>A%<\<esc>".(half - 1)."a-"
endfuntion
Refer to the line immediately after the Snip() definition in imaps.vim:
com! -nargs=0 -range Snip :<line1>,<line2>call <SID>Snip()
This defines the command Snip, which can be invoked on any range. In visual mode, vim automatically inserts the range '<,'> when you enter a command, so typing : and then Snip, which results in :'<,'>Snip, will call the function on the current selection.
Note that you can also manually specify ranges. For example, :1,5 Snip will snip lines 1 through 5, :'m,'n Snip will snip from mark m to mark n, and :Snip will apply only to the current line.
In order to avoid having to type :Snip every time, you can map this command to a key of your choice. For example, you can map z in visual mode to call Snip on the current selection as follows:
vnoremap z :Snip<CR>

How to repeatedly add text on both sides of a word in vim?

I have a bunch of local variable references in a Python script that I want to pull from a dictionary instead. So, I need to essentially change foo, bar, and others into env['foo'], env['bar'] and so on. Do I need to write a regular expression and match each variable name to transform, or is there a more direct approach that I could just repeat with the . command?
You can use a macro: type these commands in one go (with spacing just to insert comments)
" first move to start of the relevant word (ie via search)
qa " record macro into the a register.
ienv['<esc> " insert relevant piece
ea'] " move to end of word and insert relevant piece
q " stop recording
then, when you're on the next word, just hit #a to replay the macro (or even ## to repeat the last replay after that).
There's an easier way - you can use a regex search and replace. Go into cmdline mode by typing a colon and then run this command:
%s/\\(foo\|bar\|baz\\)/env['\1']/
Replacing foo, bar, and baz with whatever your actual variable names are. You can add as many additional variables as you'd like, just be sure to escape your OR pipes with a backslash. Hope that helps.
you could write a function that would do this pretty well, add this to your .vimrc file:
function! s:surround()
let word = expand("<cword>")
let command = "%s/".word."/env[\'".word."\']/g"
execute command
endfunction
map cx :call <SID>surround()<CR>
This will surround every occurance of the word currently under the cursor.
If you wanted to specify what went before and after each instance you could use this:
function! s:surround()
let word = expand("<cword>")
let before = input("what should go before? ")
let after = input("what should go after? ")
let command = "%s/".word."/".before.word.after."/g"
execute command
endfunction
map cx :call <SID>surround()<CR>
If you only want to confirm each instance of the variable you could use this:
function! s:surround()
let word = expand("<cword>")
let before = input("what should go before? ")
let after = input("what should go after? ")
let command = "%s/".word."/".before.word.after."/c"
execute command
endfunction
map cx :call <SID>surround()<CR>
I figured out one way to do what I need. Use q{0-9a-zA-Z"} to record key strokes into a buffer. Position the cursor at the begging of the variable name, then cw and type env['']. Next move the cursor back one space to the last quote and paste the buffer filled from the cw command with P. Finally, reuse the recording with #{0-9a-z".=*} for each variable.

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

Resources