Vimrc mapping not behaving as expected - vim

I have created a mapping in visual and normal mode to expedite moving around in a local region of code. if i press 1+direction key it is remapped to 10 instead of 1.
vmap 1j 10j | vmap 1k 10k | vmap 1h 10h | vmap 1l 10l
nmap 1j 10j | nmap 1k 10k | nmap 1h 10h | nmap 1l 10l
This works well. However when I am typing fast I inadvertently type 11 instead of 1 so '11j' insead of '1j'. This is moving me 110 lines down instead of 11.
I would like to only move 11 in a given direction instead of 110 when I make this mistake.
Vim is interpreting this as a 1 and then adding my mapping to get the 110. Similarly if I type '21j' it is interpreted as '210j'.

This should do what you. However I'm not really sure why it works the way it does. It seems that the old count doesn't get cleared when you change the count inside the mapping and the the new count is appended to the old count. (Notice that I only put a 0 in the mapping not a 10)
I also used v:count to find the count of the mapping instead of overloading 1j. v:count returns 0 if no count was specified.
function TenMovement(type)
if v:count == 1
return '0'.a:type
else
return a:type
endif
endfunction
nnoremap <expr> j TenMovement('j')
nnoremap <expr> k TenMovement('k')
nnoremap <expr> l TenMovement('l')
nnoremap <expr> h TenMovement('h')
vnoremap <expr> j TenMovement('j')
vnoremap <expr> k TenMovement('k')
vnoremap <expr> l TenMovement('l')
vnoremap <expr> h TenMovement('h')

To fix this, you have to abort the previously typed count. <C-\><C-n> works like <Esc> in normal mode, but avoids the beep when there's no pending count:
nmap 1j <C-\><C-n>10j
For visual mode, the selection needs to be re-established with gv:
vmap 1j <C-\><C-n>gv10j

Related

Limit count modifier to iterate commands

Vim provides the count modifier, which is used to multiply or add iterations to a command. If you use vim, you are probably familiar with it: It allows you to write 50j to move down 50 times.
Occasionally I manage to enter a big number without noticing, while I am actually using other applications. When I then proceed to use vim and for example type o to begin a new line, vim naturally tries to create a huge amount of new lines, which slowly fills up the memory and then gets killed by the kernel OOM killer.
Is there any way to limit the counter or to add a confirmation if it is greater than some threshold?
This almost works:
function! UpdateCount(n) abort
let limit = get(g:, 'counter_limit', 99)
if v:count == 0
return ''.a:n
elseif v:count == limit
return ''
elseif 10 * v:count + a:n > limit
return repeat("\<Del>", strlen(v:count)).limit
else
return ''.a:n
endif
endfunction
nnoremap <expr> 0 UpdateCount(0)
nnoremap <expr> 1 UpdateCount(1)
nnoremap <expr> 2 UpdateCount(2)
nnoremap <expr> 3 UpdateCount(3)
nnoremap <expr> 4 UpdateCount(4)
nnoremap <expr> 5 UpdateCount(5)
nnoremap <expr> 6 UpdateCount(6)
nnoremap <expr> 7 UpdateCount(7)
nnoremap <expr> 8 UpdateCount(8)
nnoremap <expr> 9 UpdateCount(9)
But, unfortunately, it doesn't work for the 0 key, since Vim disables any mappings for 0 while entering a count, which makes sense since 0 by itself is the command to go to the first character of the line and if these mappings weren't disabled then a command such as nnoremap 0 ^ would break usage of 0 in counts...
So, yeah, other than patching Vim to add a limit, I don't really see a good way to fix this in general.
If this is a problem with some commands more than others (i.e. insertion commands, such as o or i or A, etc.) then you might want to consider adding a mapping to those, inspecting v:count in those and preventing them if the count is above a certain limit.
For example:
function! LimitCount(cmd) abort
let limit = get(g:, 'counter_limit', 99)
if v:count > limit
echomsg "Count ".v:count." is too large, aborting execution of '".a:cmd."' command."
" Use Ctrl-C to erase the pending count and avoid applying it to the next command.
return "\<C-C>"
else
return a:cmd
endif
endfunction
nnoremap <expr> o LimitCount('o')
nnoremap <expr> i LimitCount('i')
" And so on for other insertion commands...

Is there a way to expand a Vim fold automatically when your put your cursor on it?

Can you have Vim expand a fold automatically when the cursor touches it?
See the foldopen option. It controls which groups of commands will lead to
opening a fold if the cursor is moved into a closed fold.
Note that vertical movements do not open a closed fold, though. Moreover,
there is no setting in foldopen to enable this behavior. When hor item is
set in foldopen option, to open a fold one can use h, l or other
horizontal movement commands. In case if it is crucial to automatically open
a fold on any cursor movement that touches it, one can approach this problem
by remapping some subset of vertical movement commands like it is shown below.
nnoremap <silent> j :<c-u>call MoveUpDown('j', +1, 1)<cr>
nnoremap <silent> k :<c-u>call MoveUpDown('k', -1, 1)<cr>
nnoremap <silent> gj :<c-u>call MoveUpDown('gj', +1, 1)<cr>
nnoremap <silent> gk :<c-u>call MoveUpDown('gk', -1, 1)<cr>
nnoremap <silent> <c-d> :<c-u>call MoveUpDown("\<lt>c-d>", +1, '&l:scroll')<cr>
nnoremap <silent> <c-u> :<c-u>call MoveUpDown("\<lt>c-u>", -1, '&l:scroll')<cr>
nnoremap <silent> <c-f> :<c-u>call MoveUpDown("\<lt>c-f>", +1, 'winheight("%")')<cr>
nnoremap <silent> <c-b> :<c-u>call MoveUpDown("\<lt>c-b>", -1, 'winheight("%")')<cr>
function! MoveUpDown(cmd, dir, ndef)
let n = v:count == 0 ? eval(a:ndef) : v:count
let l = line('.') + a:dir * n
silent! execute l . 'foldopen!'
execute 'norm! ' . n . a:cmd
endfunction
An inferior, but a bit thriftier solution would be to open a fold on every
cursor movement.
autocmd CursorMoved,CursorMovedI * silent! foldopen
Unfortunately, this solution is not general one. After the fold under the
cursor is opened, the cursor is positioned on the first line of that fold. If
this behavior is undesirable, one can follow the vertical direction of
a movement, and place the cursor on the last line of the fold when the cursor
is moving bottom-up.
autocmd CursorMoved,CursorMovedI * call OnCursorMove()
function! OnCursorMove()
let l = line('.')
silent! foldopen
if exists('b:last_line') && l < b:last_line
norm! ]z
endif
let b:last_line = l
endfunction
However, a fold will not be opened if the movement jumps over the fold. For
example, 2j on the line just above a fold will put the cursor on the line
just after that fold, not the second line in it.
set foldopen=all
seems to do what you want. You may also make an autocommand for cursor movement:
au CursorMoved * call AutoOpen()
calling a function like:
function! AutoOpen()
if foldclosed(".") == line(".")
call feedkeys("zo")
endif
endfunction
If you want this to also work in insert mode, use:
au CursorMoved,CursorMovedI * call AutoOpen()
:help fdo and possibly :help fcl may help you. I have this line in my .vimrc:
set foldopen=block,hor,insert,jump,mark,percent,quickfix,search,tag,undo

Setting to skip over punctuation when moving forwards and backwards words

When I'm using vim I generally never want to move to a punctuation mark when I press w or b to go forwards or backwards. So I'm wondering if there's a setting or something to change this functionality?
e.g. If I've got some code like
object.method(args)
and my cursor is at the [o] in "object" then I want w to move to the [m] in "method", and another w to move to the [a] in "args". I don't want it to land on the [.] or the [(]. If I've ever wanted to move to a punctuation char I've always used f or F to jump straight to it. I've never personally wanted to move to a punctuation char when I move through words and I just realized this is really bugging me.
I too find that I would like a movement that is more inclusive that w, but not as inclusive as W. In particular, I would like a movement that only considers tokens beginning with alphanumeric characters as significant.
So I came up with the following:
" <SPACE> : forward to next word beginning with alphanumeric char
" <S-SPACE> : backward to prev word beginning with alphanumeric char
" <C-SPACE> : same as above (as <S-SPACE> not available in console Vim
" <BS> : back to prev word ending with alphanumeric char
function! <SID>GotoPattern(pattern, dir) range
let g:_saved_search_reg = #/
let l:flags = "We"
if a:dir == "b"
let l:flags .= "b"
endif
for i in range(v:count1)
call search(a:pattern, l:flags)
endfor
let #/ = g:_saved_search_reg
endfunction
nnoremap <silent> <SPACE> :<C-U>call <SID>GotoPattern('\(^\\|\<\)[A-Za-z0-9_]', 'f')<CR>
vnoremap <silent> <SPACE> :<C-U>let g:_saved_search_reg=#/<CR>gv/\(^\\|\<\)[A-Za-z0-9_]<CR>:<C-U>let #/=g:_saved_search_reg<CR>gv
nnoremap <silent> <S-SPACE> :<C-U>call <SID>GotoPattern('\(^\\|\<\)[A-Za-z0-9_]', 'b')<CR>
vnoremap <silent> <S-SPACE> :<C-U>let g:_saved_search_reg=#/<CR>gv?\(^\\|\<\)[A-Za-z0-9_]<CR>:<C-U>let #/=g:_saved_search_reg<CR>gv
nnoremap <silent> <BS> :call <SID>GotoPattern('[A-Za-z0-9_]\(\>\\|$\)', 'b')<CR>
vnoremap <silent> <BS> :<C-U>let g:_saved_search_reg=#/<CR>gv?[A-Za-z0-9_]\(\>\\|$\)<CR>:<C-U>let #/=g:_saved_search_reg<CR>gv
" Redundant mapping of <C-SPACE> to <S-SPACE> so that
" above mappings are available in console Vim.
"noremap <C-#> <C-B>
if has("gui_running")
map <silent> <C-Space> <S-SPACE>
else
if has("unix")
map <Nul> <S-SPACE>
else
map <C-#> <S-SPACE>
endif
endif
I have had this for a long time now, and I find that I use <SPACE>/<C-SPACE> movements so much more than w and W; it just seems more useful when coding. You can, of course, map the commands to whatever keys you find useful or more appropriate.
Even running the risk of creating a script for something that's built-in (like
I did last time), here is a little function that may help accomplishing
this.
function! JumpToNextWord()
normal w
while strpart(getline('.'), col('.')-1, 1) !~ '\w'
normal w
endwhile
endfunction
Basically, what it does is executing the standard w and repeating it
if the character under the cursor is not in a word character (feel free to
change that pattern.
If you add that and a little map in your .vimrc:
nnoremap <silent> ,w :call JumpToNextWord()<CR>
It should work.

Unable to move upwards by t in Vim's Tlist when I use Dvorak

Problem: to move upwards in Vim's Taglist by "t"
The movement keys DHTN work in Vim when I am not in TagList.
I have the following in my .vimrc
no h j
no t k
no n l
no s :
no S :
no j d
no J D
no l n
no L N
no - $
no _ ^
no N
no ; z
no T L
no P P
no p p
How can you enable the movement key "t" also in TagList?
The problem is that Tag List has defined very specific action to these keys, so rebinding them has moved functionality on top of it and can not be used to shift responsibility. There might be another way, but you can edit taglist.vim at line :1560 and :1562
nnoremap <buffer> <silent> t
nnoremap <buffer> <silent> <C-t>
change 't' to the letter you want, maybe 'l'. You will also find all the other key bindings in this area. While not needed or affected by these changes, you can also update the help message if you change other bindings starting at line :535
The problem can be solved by adding the following to your .vimrc
if v:version >= 700
nnoremap <buffer> <silent> t
\
nnoremap <buffer> <silent> <C-t>
\
endif
Response to Great's question:
I remaped the key unsuccessfully by adding the following to my .vimrc
if v:version >= 700
nnoremap <buffer> <silent> l
\ :call <SID>Tlist_Window_Jump_To_Tag('checktab')<CR>
nnoremap <buffer> <silent> <C-l>
\ :call <SID>Tlist_Window_Jump_To_Tag('newtab')<CR>
endif
How would you do the remap?

How to paste over without overwriting register

Does anyone know of a way to paste over a visually selected area without having the selection placed in the default register?
I know I can solve the problem by always pasting from an explicit register. But it's a pain in the neck to type "xp instead of just p
Use the following:
xnoremap p pgvy
this will reselect and re-yank any text that is pasted in visual mode.
Edit: in order this to work with "xp you can do:
xnoremap p pgv"#=v:register.'y'<cr>
v:register expands to the last register name used in a normal mode command.
I don't like the default vim behavior of copying all text deleted with d, D, c, or C into the default register.
I've gotten around it by mapping d to "_d, c to "_c, and so on.
From my .vimrc:
"These are to cancel the default behavior of d, D, c, C
" to put the text they delete in the default register.
" Note that this means e.g. "ad won't copy the text into
" register a anymore. You have to explicitly yank it.
nnoremap d "_d
vnoremap d "_d
nnoremap D "_D
vnoremap D "_D
nnoremap c "_c
vnoremap c "_c
nnoremap C "_C
vnoremap C "_C
"{register}p won't work as you describe. It will replace the selection with the content of the register. You will have instead to do something like:
" I haven't found how to hide this function (yet)
function! RestoreRegister()
let #" = s:restore_reg
return ''
endfunction
function! s:Repl()
let s:restore_reg = #"
return "p#=RestoreRegister()\<cr>"
endfunction
" NB: this supports "rp that replaces the selection by the contents of #r
vnoremap <silent> <expr> p <sid>Repl()
Which should be fine as long as you don't use a plugin that has a non-nore vmap to p, and that expects a register to be overwritten.
This code is available as a script there. Ingo Karkat also defined a plugin solving the same issue.
In your .vimrc
xnoremap p "_dP
I found this from a response on a similar thread, but the original source was http://vim.wikia.com/wiki/Replace_a_word_with_yanked_text. It mentions some drawbacks, however it works fine for me.
Luc Hermitte's solution works like a charm. I was using it for about a week or so. Then I discovered a solution from Steve Losh's .vimrc that works nicely if YankRing is part of your plugin/bundle lineup:
function! YRRunAfterMaps()
" From Steve Losh, Preserve the yank post selection/put.
vnoremap p :<c-u>YRPaste 'p', 'v'<cr>gv:YRYankRange 'v'<cr>
endfunction
Try this in your ~/.vimrc:
xnoremap <expr> p 'pgv"'.v:register.'y'
xnoremap means that this is only for Visual mode, not Visual + Select modes.
<expr> means that {rhs} of the xnoremap {lhs} {rhs} setting is evaluated as an expression.
In this case, our expression of 'pgv"'.v:register.'y' is using . for concatenation.
v:register is evaluated to the register being used during the fulfillment of the mapping.
The result of "xp would evaluate to pgv"xy, where x is the register.
I was helped by an answer to this stackoverflow question: Vim - mapping with an optional register prefix
in conjunction with Benoit's answer on this page
Luc's function worked well for me after I made a change to support the fact that I have clipboard=unnamed set:
function! RestoreRegister()
let #" = s:restore_reg
if &clipboard == "unnamed"
let #* = s:restore_reg
endif
return ''
endfunction
Use P to paste without yanking the deleted text.
:help v_P
With P the unnamed register is not changed (and neither the selection or clipboard), you can repeat the same change.
This behavior was introduced in v8.2.4242 (2022-01-28) and refined in v8.2.4881 (2022-05-06).
Or if your muscle memory is too strong:
xnoremap p P
Luc Hermitte's did the trick! Really good. Here's his solution put in a toggle function, so you can switch between normal behavior and no-replace-register put.
the command ,u toggles the behavior
let s:putSwap = 1
function TogglePutSwap()
if s:putSwap
vnoremap <silent> <expr> p <sid>Repl()
let s:putSwap = 0
echo 'noreplace put'
else
vnoremap <silent> <expr> p p
let s:putSwap = 1
echo 'replace put'
endif
return
endfunction
noremap ,p :call TogglePutSwap()<cr>
This is my solution.
vnoremap p p:let #+=#0<CR>
vnoremap P P:let #+=#0<CR>
I find out after paste, the old content is still stored in "0 register.
Just restore it to current clipboard by
:let #+=#0
duct-tape programming, but works for me:
nmap viwp viwpyiw
nmap vi'p vi'pyi'
nmap vi"p vi"pyi"
nmap vi(p vi(pyi(
nmap vi[p vi[pyi[
nmap vi<p vi<pyi<
Select the text and paste by P(uppercase).
Example:
viwP
See h: v_P for more infomation.
try -
:set guioptions-=a
:set guioptions-=A

Resources