Using Vim command to insert text to multiple lines - vim

I know in visual block mode, by <S-i> (I) one can insert in multiple selected lines, however I want to achieve the same effect by a function, let's say I have a functions which can tell the three sub-visual modes (visual-character, visual-line, visual-block) as follows,
function! VisualMappingSpace()
let m = visualmode()
if m ==# 'v'
echo 'character-wise visual'
elseif m == 'V'
echo 'line-wise visual'
elseif m == "\<C-V>"
echo 'block-wise visual'
endif
endfunction
I've tried as follows but it doesn't work. I want to insert soemthing to the lines I select when I hit <space> in visual-block mode.
function! VisualMappingSpace()
let m = visualmode()
if m ==# 'v'
exec "normal y"
elseif m == 'V'
exec "normal y"
elseif m == "\<C-V>"
let g:block_insert_content = input("")
exec "normal I ".g:block_insert_content
endif
endfunction
vnoremap <silent> <Space> :call VisualMappingSpace()<CR>

A visual-mode mapping that enters command-line mode via : will have the visual range ('<,'>) automatically inserted. With :call, that means that your function is invoked once per selected line. You should have noticed via the repeated queries.
To avoid this, insert <C-u> into your mapping; it clears the range.
Second problem: When you insert the queried text, you need to re-create the selection (your mapping left visual mode for command-line mode, remember?) via gv; then, I will work:
function! VisualMappingSpace()
let m = visualmode()
if m ==# 'v'
exec "normal y"
elseif m == 'V'
exec "normal y"
elseif m == "\<C-V>"
let g:block_insert_content = input("")
exec "normal gvI ".g:block_insert_content
endif
endfunction
vnoremap <silent> <Space> :<C-u>call VisualMappingSpace()<CR>
Also note that there is an additional space character before your queried text; I'm not sure you want that: gvI ".

Related

Move to character and drop into insert mode in VIM

I recently fell in love with the f, F, t, and T commands in vim. Now I find myself frequently wanting to insert something at a position that can easily be navigated to with one of these commands, at least frequently enough that I would want to make the entire action "find character and insert text" repeatable via .. Currently, I can repeat the insert action, but I have to retype the find-character movement at every line where I want to repeat the insert.
Is there a command that combines the actions of these movement commands with the action of dropping into insert mode? Or, if not, is it possible to define such a command in my .vimrc?
First, you can repeat the last f/t/F/T motion via ; (reverse via ,), so you can repeat with two keys: ;.
If that's not good enough, the repeat.vim plugin can be used to build a custom mapping that repeats just like the built-in commands:
"<Leader>it{char} Insert text before the [count]'th occurrence of {char}
" to the right.
"<Leader>if{char} Insert text after the [count]'th occurrence of {char}
" to the right.
" These mappings can be repeated atomically, this is
" faster than ";."
function! s:InsertAtCharPrepare( motion, moveOffMotion, repeatMapping )
augroup InsertAtChar
autocmd!
" Enter insert mode automatically after the f/t motion.
" XXX: :startinsert doesn't work on the first movement somehow, use
" feedkeys() instead.
autocmd CursorMoved <buffer> call feedkeys('a', 'n')
" Prime repeat.vim after insertion is done.
execute printf('autocmd InsertLeave <buffer> %scall repeat#set(%s, %d) | autocmd! InsertAtChar',
\ (v:count1 <= 1 || empty(a:moveOffMotion) ? '' : 'execute "normal!" ' . string(a:moveOffMotion) . '|'),
\ string(a:repeatMapping),
\ v:count1
\)
" Abort in case something unexpected happens.
autocmd WinLeave,BufLeave <buffer> autocmd! InsertAtChar
augroup END
return a:motion
endfunction
function! s:InsertAtCharRepeat( moveOffMotion, repeatMapping )
let l:count = v:count1 " Save the original count to pass this on to repeat.vim.
execute 'normal!' l:count . ';.' . (l:count <= 1 ? '' : a:moveOffMotion)
call repeat#set(a:repeatMapping, l:count)
endfunction
" With "t" and [count] > 1, we need to move off from before {char} (where we're
" left when leaving insert mode) onto {char}, so that a repeat will happen
" before the next occurrence, not on the same again.
nnoremap <silent> <Plug>(InsertUntilCharRepeat) :<C-u>call <SID>InsertAtCharRepeat('l', "\<lt>Plug>(InsertUntilCharRepeat)")<CR>
nnoremap <silent> <Plug>(InsertFromCharRepeat) :<C-u>call <SID>InsertAtCharRepeat('', "\<lt>Plug>(InsertFromCharRepeat)")<CR>
nnoremap <expr> <Leader>it <SID>InsertAtCharPrepare('t', 'l', "\<lt>Plug>(InsertUntilCharRepeat)")
nnoremap <expr> <Leader>if <SID>InsertAtCharPrepare('f', '', "\<lt>Plug>(InsertFromCharRepeat)")

Inserting two new lines in Vim?

Is it possible to do this with a Vim command?
[] = Cursor Normal Mode
[ = Cursor Insert Mode
Before
Text []
After
Text
[
Before
Text []
After
[
Text
I've changed the [count] behavior of o / O with the following mapping. I think this does what you want:
" o/O Start insert mode with [count] blank lines.
" The default behavior repeats the insertion [count]
" times, which is not so useful.
function! s:NewLineInsertExpr( isUndoCount, command )
if ! v:count
return a:command
endif
let l:reverse = { 'o': 'O', 'O' : 'o' }
" First insert a temporary '$' marker at the next line (which is necessary
" to keep the indent from the current line), then insert <count> empty lines
" in between. Finally, go back to the previously inserted temporary '$' and
" enter insert mode by substituting this character.
" Note: <C-\><C-n> prevents a move back into insert mode when triggered via
" |i_CTRL-O|.
return (a:isUndoCount && v:count ? "\<C-\>\<C-n>" : '') .
\ a:command . "$\<Esc>m`" .
\ v:count . l:reverse[a:command] . "\<Esc>" .
\ 'g``"_s'
endfunction
nnoremap <silent> <expr> o <SID>NewLineInsertExpr(1, 'o')
nnoremap <silent> <expr> O <SID>NewLineInsertExpr(1, 'O')
do these two mapping help?
nnoremap <leader>O O<ESC>O
nnoremap <leader>o o<cr>
the first by pressing <leader>O will add two empty lines above current line, and bring you to INSERT mode. The 2nd one by pressing <leader>o will add two lines after your current.

How can I make the tilde operator change ‘==’ to ‘!=’ in Vim?

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

Vim: how to make a macro/command that will wait for a key and use it?

For example, I want to temporarily map to fxsj. That is, when I press q, VIM will perform fxsqj. When I press k, VIM will perform fxskj. And so on.
You can use getchar(), for example:
nnoremap <F2> :call Fun()<CR>
function! Fun()
let c = nr2char(getchar())
if c=='q' || c=='k'
exec 'normal fxs'.c.'j'
endif
endfunction

change from insert to normal mode when switching to another tab?

Say I have multiple tabs with multiple buffers in split screens.
When I am in edit mode in one buffer and switch to another tab (ctrl-pageDown), I am still in insert mode.
Is there a way to automatically switch to normal mode when changing tabs ?
Even better, is it possible to return to insert mode when coming back to the original buffer ?
You could try adding something very simple like
autocmd TabEnter * stopinsert
to your .vimrc.
In BufLeave you could call a function which would check what mode you're in and set a buffer variable and then in BufEnter check if it exists and go to that mode.
See help on mode(), b:var.
Here is some sample stuff for .vimrc. Having written it just now for this purpose, I've started using it myself and I think it'll be useful.
au BufLeave * call ModeSelectBufLeave()
au BufEnter * call ModeSelectBufEnter()
function! ModeSelectBufLeave()
let b:mode_select_mode = mode()
" A more complex addition you could make: if mode() == v, V, <C-V>, s, S, or <C-S>, store the selection and restore it in ModeSelectBufEnter
endfunction
function! ModeSelectBufEnter()
let l:mode = mode()
stopinsert " First, go into normal mode
if (l:mode == "i" || l:mode == "R" || l:mode == "Rv") &&
\ (!exists('b:mode_select_mode') ||
\ b:mode_select_mode == "n" ||
\ b:mode_select_mode == "v" ||
\ b:mode_select_mode == "V" ||
\ b:mode_select_mode == "\<C-V>" ||
\ b:mode_select_mode == "s" ||
\ b:mode_select_mode == "S" ||
\ b:mode_select_mode == "\<C-S>")
normal l
" Compensate for the left cursor shift in stopinsert if going from an
" insert mode to a normal mode
endif
if !exists('b:mode_select_mode')
return
elseif b:mode_select_mode == "i"
startinsert
elseif b:mode_select_mode == "R"
startreplace
elseif b:mode_select_mode == "Rv"
startgreplace
endif
endfunction
I have the following in my .vimrc:
nmap <C-b> :b#<CR>
imap <C-b> <ESC>:b#<CR>
This lets me hit Ctrl+b when in normal or insert mode to switch to the alternate buffer but leaving me in normal mode.
As for your question, you could do this:
imap <C-b> <ESC>:bnext<CR>i
This will let you hit Ctrl+b when in insert mode and switch to the next buffer putting you in insert mode when you get there.
If you find yourself switching back and forth between the same two buffers, my original mappings above may be more useful. Of course if you use all three, you'll need a different key combination for the last one.

Resources