I often am editing a file (e.g., a zone file) where I have things like: Whatever-AP-1 and want to quickly change it to Whatever-AP-2. It feels right to hit Control-A (increment), but of course Control-X (decrement) is required, because Vim sees that as a negative number. Just swapping the mapping, however, won't work because then e.g., IP addresses would be backwards.

I can think of two solutions that would be fine, and (at least for me) not really break Control-A/X elsewhere:

  • Have increment increase the absolute value, decrement decrease it (control-X on 0 would do nothing — or I guess skip to the next number). Control-A on -1 givces -2; on +1, gives +2.
  • Not treat hyphenated numbers as negative. Only recognize a hyphen/minus character as a negative sign if its preceded by whitespace, a symbol, or beginning of line.

I think I'd prefer the later; but either is OK—especially if there is just an option I missed, not having to re-implement increment/decrement in vimscript (like swapit, monday, speeddating, etc. all do).

  • I don't think there is a built-in way to do what you want. I tried to implement a solution here it seems that it (kind of) works but there are probably some edge cases that I didn't test.
    – statox
    Sep 20 '18 at 12:50
  • Why not use visual mode, simply selecting only the numbers without the - and use <C-A> <C-X> then. That should work. Sep 21 '18 at 5:53
  • @ChristianBrabandt that's a lot more keystrokes, AFAIK.
    – derobert
    Sep 21 '18 at 5:57
  • @ChristianBrabandt You lose the convenience of quickly bumping a number. Besides, if you go to the trouble of selecting the number, you might as well press c and type in the new number yourself.
    – Tommy A
    Sep 21 '18 at 23:54

I've been annoyed by this for as long as I can remember. Your question prompted me to scratch this itch.

This script remaps <c-a> and <c-x> to perform custom incrementing/decrementing, but only when the number is preceded by a keyword character and a dash, or just preceded by a period. If those conditions aren't met, the default <c-a> and <c-x> keys are used.

In your case with Whatever-AP-1, pressing <c-a> on the 1 would make it 2. Pressing <c-x> would make it 0, and pressing <c-x> on the 0 would leave it at 0. With the script also recognizing numbers preceded with a period, adjusting floating point numbers will work as expected, too. That would also apply to IP addresses (except for the first octet since it doesn't meet the conditions mentioned above).

The limitation is that this won't work in visual mode. I guess it's also worth mentioning that it updates the . register, since the default keys don't.

I've added comments to the script to make it a learning experience.

function! s:incr(dir) abort
  " Search backwards from the cursor for digits preceded by a keyword
  " character and a dash/period, restricted to the current line.  Characters
  " that come after the cursor must only be digits.
  " This is slightly different from how <c-a> and <c-x> works since they
  " search forward by default.
  " If there are no matches, return the default incr/decr key.
  if search('\%(\%(\k-\)\|\.\)\@<=\d\+\&\%#\d*\>', 'cbW', line('.')) == 0
    return (a:dir > 0 ? "\<c-a>" : "\<c-x>")

  " Get the current number under the cursor.
  " Because of the search() call above, the cursor should now be placed at
  " the beginning of the number allowing us to use <cword> expansion.
  let n = expand('<cword>') + 0

  " If the number is already zero and we are subtracting, return nothing to
  " avoid creating an undo point.
  if n == 0 && a:dir < 0
    return ''

  " Clear pending mode if we started with a count
  let seq = "\<esc>"

  " The cursor will reset once the function exits.  We retain it by using a
  " column jump.
  let seq .= col('.') . '|'

  " Begin a change, throwing the <cword> into the black hole register.
  let seq .= '"_cw'

  " Incr or decr the number by v:count1, without going negative, then type it.
  let seq .= max([0, n + (v:count1 * a:dir)])

  " Press escape
  return seq . "\<esc>"

nnoremap <expr><silent> <c-a> <sid>incr(1)
nnoremap <expr><silent> <c-x> <sid>incr(-1)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.