How can I make the tilde operator change ‘==’ to ‘!=’ in Vim? - 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

Related

How to make f and t wrap around the current line in vim

Is there a way to make the 'f' and 't' command wrap around the line? For example, if I have
Hello, my name is _intz,
where _ denotes my cursor position, I would like to be able to press fl for vim to place my cursor on the first l on the line.
Similarly, I would ideally like the , and ; commands to also wrap on the current line.
Thank you
No, this is not possible without implementing the feature yourself.
Note that fF are universally expected to mean "next on the line" and tT to mean "previous on the line", both of which being extremely useful in their own right. Instead of changing their meaning, and thus reducing the overall usefulness of Vim, you should consider making new commands.
Something like these quick and dirty mappings:
" move the cursor on first occurrence of character on the line
nnoremap <expr> <key> '0f' . nr2char(getchar())
" move the cursor before first occurrence of character on the line
nnoremap <expr> <key> '0t' . nr2char(getchar())
See :help <expr>, :help nr2char(), :help getchar().
With the help of this answer https://vi.stackexchange.com/questions/29167/determine-if-there-is-a-matching-character-on-the-current-line-past-the-cursor, the following maps <c-f> to allow that gives it the functionality of f with same line wrapping.
function!Neweff()
let character = nr2char(getchar())
let beforejump = getpos('.')
execute 'norm! f'.character.''
let afterjump = getpos('.')
if beforejump == afterjump
let firstcharacter = getline(".")[0]
execute 'norm! 0'
if character !=# firstcharacter
execute 'norm! f'.character.''
endif
endif
endfunction
nnoremap <c-f> :call Neweff()<CR>

Is it possible to save yanked history in numbered register? (without plugin)

In Vim, as far as I know, the numbered register saves only the deleted history.
Is it possible to make it save also the yanked one?
(without plugin)
Thanks
Is it possible to make it save also the yanked one?
In fact, the registers 2-9 are rarely useful, as we can save all the important pieces into "letter" registers as needed. And there's "undo" for anything lost anyway.
without plugin
Plugin is just a piece of code. You can do without a plugin, but you can't do without code.
" Shift previously yanked text through the numbered registers 0, 2-9
" note:
" the register 1 is reserved for deletion
" there's no "small yank" register
" can break :h redo-register
" $-blocks are broken
nnoremap <silent>y :set opfunc=YankeeDoodle<CR>g#
nnoremap <silent>Y :k] \| k[ \| call YankeeDoodle("line")<CR>
nnoremap <silent>yy :k] \| k[ \| call YankeeDoodle("line")<CR>
vnoremap <silent>y :<C-U>call YankeeDoodle(visualmode())<CR>
vnoremap <silent>Y :<C-U>call YankeeDoodle("V")<CR>
let s:ydict = #{char: "v", line: "V", block: "\<C-V>"}
function! YankeeDoodle(type)
if has_key(s:ydict, a:type)
let l:type = s:ydict[a:type]
let l:mark = ['[', ']']
else
let l:type = a:type
let l:mark = ['<', '>']
endif
if v:register is '"'
for l:regno in range(8, 2, -1)
call setreg(l:regno + 1, getreg(l:regno), getregtype(l:regno))
endfor
call setreg(2, getreg(0), getregtype(0))
endif
" go to start mark; register override; yank; forced-motion; move to end mark
call execute(printf('normal! g`%s"%sy%sg`%s', l:mark[0], v:register, l:type, l:mark[1]), '')
endfunction
UPD. As #PeterRincker suggests we can use TextYankPost instead. The following code must be more robust, but still not 100%, as we must keep track of the register 0 manually (TextYankPre will be much better here, but, alas, we don't have it).
" note:
" the register 1 is reserved for deletion
" there's no "small yank" register
" can break :h redo-register
" still misses any manual register 0 change
augroup YankShift | au!
let s:regzero = [getreg(0), getregtype(0)]
autocmd TextYankPost * call <SID>yankshift(v:event)
augroup end
function! s:yankshift(event)
if a:event.operator ==# 'y' && (empty(a:event.regname) || a:event.regname == '"')
for l:regno in range(8, 2, -1)
call setreg(l:regno + 1, getreg(l:regno), getregtype(l:regno))
endfor
call setreg(2, s:regzero[0], s:regzero[1])
let s:regzero = [a:event.regcontents, a:event.regtype]
elseif a:event.regname == '0'
let s:regzero = [a:event.regcontents, a:event.regtype]
endif
endfunction

Using Vim command to insert text to multiple lines

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

Understand :iabbrev <buffer> iff if:<left>

I typed :autocmd FileType python :iabbrev <buffer> iff if:<left> as this tutorial told.
The output was
if :
Why is there a space between if and ":"?
I assume you're using the space bar after you type iff? If so, it's because of the <left>. This is positioning your cursor one to the left, i.e. between the f and the ":". Once the space bar is accepted your cursor is in between the two characters so it puts a space between them. You can try the command without the <left> and see if that does what you need. If not, you'll need to let us know exactly what output you're looking for us to be able to help you. Also see: :help abbrev if you haven't already.
Abbreviation's are triggered by non-keyword (e.g. ., <cr>, <space>, etc), <esc>, or <c-]>. Typing iff alone will is not enough to expand the abbreviation. You typed iff<space> which is enough to expand the abbreviation and puts the <space> inside your expanded abbreviation. You can use <c-]> to expand abbreviations without inserting any extra characters. e.g. iff<c-]>
Eatchar
I however find using <c-]> to be unappealing. Vim's documentation gives us an alternative, the Eatchar function. This function will consume a key matching some pattern and not output it.
function! Eatchar(pat)
let c = nr2char(getchar(0))
return (c =~ a:pat) ? '' : c
endfunction
iabbr <buffer> iff if:<left><c-r>=Eatchar('\s')<cr>
Rails.vim like abbreviations
You can take this even further and make Rails.vim-esque abbreviations which only expand on <tab> or a supplied pattern. Think of these as lightweight snippets.
function! RailsExpand(root, good, ...)
let c = nr2char(getchar(0))
if c == "" || c =~ (a:0 ? a:1 : "\t")
return a:good
else
return a:root . c
endif
endfunction
iabbr <buffer> iff <c-r>=RailsExpand('iff', "if:\<left>")<cr>
Now iff<tab> will expand properly. However defining abbreviations like this is a mess.
function! Railsabbrev(root, good)
let good = substitute(a:good, '[\"|]', '\\&', "g")
let good = substitute(good, '<', '\\<lt>', "g")
let root = substitute(a:root, '[\"|]', '\\&', "g")
let root = substitute(root, '<', '\\<lt>', "g")
execute "iabbr <buffer> " . a:root . " <c-r>=RailsExpand(\"" . root . "\", \"" . good . "\")<cr>"
endfunction
command! -nargs=* Railsabbrev call Railsabbrev(<f-args>)
Now you can use :Railsabbrev to define your <tab> expanding abbreviation. Example:
Railsabbrev iff if:<left>
Snippets
Sometimes abbreviations are just too simple or too tricky to maintain for multiline expansions. If this is the case I suggest you look for a good snippet plugin. Good choices are UltiSnips or vim-snipmate. Look at their documentation on how to expand and create your own snippets.
More help
:h Abbreviations
:helpg Eatchar

Is there any easy way to toggle "do/end" and "{}" in ruby in Vim?

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

Resources