10

Is there a way to increase/decrease first digit after/under cursor, not the whole number.

I know it's not how vim works, but is there a plugin that does that? I was unable to find it.

It is very useful when editing css and in other cases too.

Given:

▐margin: 10px

When I press CTRL-A I want to see:

▐margin: 20px

Actual:

▐margin: 11px
  • 10
    You can also do 10<C-a> to add 10 or r2 to replace the 1 with a 2. – jamessan Apr 14 '15 at 1:32
6

This seems to work quite well, and does fairly sane things when using <C-a> on a 9 (95 becomes 105), or using <C-x> on a 0 (105 becomes 95):

nnoremap g<C-a> :call search('\d', 'c')<CR>a <Esc>h<C-a>lxh
nnoremap g<C-x> :call search('\d', 'c')<CR>a <Esc>h<C-x>lxh
  • First, we search for any digit, search() has the advantage of not resetting @/.
  • a <Esc>h - Go to insert mode, add a space after the digit, and go to the left so that the cursor is on top of the digit.
  • We can now use <C-a> or <C-x>.
  • We use lxh to remove the space we added.

I've mapped this to g<C-a> and g<C-x>, so you can still call the original.

A slightly different version which will only search for numbers on the current line (but will leave a dangling space if there's no number on the current line):

nnoremap g<C-a> :call search('\d', 'c', line('.'))<CR>a <Esc>h<C-a>lxh
nnoremap g<C-x> :call search('\d', 'c', line('.'))<CR>a <Esc>h<C-x>lxh

And here's another version which uses the same concept as before, but also adds a space before the digit. This will make g<C-a> ignore any munis sign before the number (by default, <C-a> on -42 will 'increment' it to -41.

It also accepts a count, so that 5g<C-a> will increment the number by 5:

fun! Increment(dir, count)
    " No number on the current line
    if !search('\d', 'c', getline('.'))
        return
    endif

    " Store cursor position
    let l:save_pos = getpos('.')

    " Add spaces around the number
    s/\%#\d/ \0 /
    call setpos('.', l:save_pos)
    normal! l

    " Increment or decrement the number
    if a:dir == 'prev'
        execute "normal! " . repeat("\<C-x>"), a:count
    else
        execute "normal! " . repeat("\<C-a>", a:count)
    endif

    " Remove the spaces
    s/\v (\d{-})%#(\d) /\1\2/

    " Restore cursor position
    call setpos('.', l:save_pos)
endfun

nnoremap <silent> g<C-a> :<C-u>call Increment('next', v:count1)<CR>
nnoremap <silent> g<C-x> :<C-u>call Increment('prev', v:count1)<CR>
9

Basic increment

Here is a simple macro to perform the action:

:nnoremap <leader>a m`lv$xh<c-a>p``
:nnoremap <leader>x m`lv$xh<c-x>p``

In normal mode you

  • m` Mark your location
  • l move one character to the right
  • v$x cut to the end of the line
  • h move back to the original position
  • <c-a> increment (or decrement)
  • p paste back your cut
  • `` move back to your mark

Jump to the next number

If you want to jump to the next number (or stay in your current position if on a number) you need a function that checks the current cursored character and possible jump to the next number.

function! NextNum()
  let ch = getline(".")[col(".")-1]
  if ch !~ "[0-9]"
    execute "normal! /[0-9]\<cr>"
  endif
endfunction

nnoremap <leader>a :call NextNum()<cr>m`lv$xh<c-a>p``
nnoremap <leader>x :call NextNum()<cr>m`lv$xh<c-x>p``

NextNum gets the character under the cursor, checks if its a number and if not searches for the next number. After that the rest is the same. If you want the mapping different just change the nnoremap <leader>a to what you wish, for example nnoremap <c-a>.

Ignoring negatives and numbers higher than 9

If you want to just cycle through digits and not have them act as signed integers the following functions will increment and decrement and roll over at 0 and 9.

function! NextNum()
  let ch = getline(".")[col(".")-1]
  if ch !~ "[0-9]"
    execute "normal! /[0-9]\<cr>"
  endif
endfunction

function! IncDec(val, dec)
  if a:dec
    if a:val == 0
      return 9
    else
      return a:val - 1
    endif
  else
    if a:val == 9
      return 0
    else
      return a:val + 1
    endif
  endif
endfunction

function! DoMath(dec)
  call NextNum()
  normal! x
  let @" = IncDec(@", a:dec)
  normal! P
endfunction

nnoremap <leader>a :call DoMath(0)<cr>
nnoremap <leader>x :call DoMath(1)<cr>

Now when you are on 8 and type <leader>a you get 9. Doing it again results in 0. If you press <leader>x on 0 you get 9. Same goes for negative numbers. The functions cut a single character, increment, decrement or roll over and then paste in place.

  • Something isn't right here, but I'll keep that idea in mind, maybe it can be improved. It should be jumping to the first digit on the current line if any and operate on it. Preferably be mappable to <c-a>/<c-x> also. – firedev Apr 14 '15 at 3:54
  • The updated version. Now jumps forward to the next number or if you are currently over a digit allows you to increment it. – jecxjo Apr 14 '15 at 11:56
  • Yes! This is amazing, thank you! I don't understand why the question was downvoted though, this was annoying me since the day one and apparently many people redefine <c-a> and <c-x> to something else because the default behaviour is not that useful. – firedev Apr 15 '15 at 4:39
  • I could see it gets downvoted because the question was for something very specific and the original question sort of changed along the way...the need to jump to the first number. That aside, I actually use <c-a> and <c-x> a lot. Any time I update a version or increase a range variable, I just [N]<c-a> instead of deleting and replacing. – jecxjo Apr 15 '15 at 5:13
  • 1
    It would be possible but would be a much larger set of functions. The current implementation takes advantage of <c-a> and <c-x> which would need to be dropped for a function that does the same but ignores the dash. – jecxjo Apr 17 '15 at 13:31
5

Here's a version I wrote using a substitute expression:

map <c-a> :s/\d/\=submatch(0) < 9 ? submatch(0) + 1 : submatch(0)/<CR>
map <c-x> :s/\d/\=submatch(0) > 0 ? submatch(0) - 1 : submatch(0)/<CR>

Each one just looks for the first digit character on the line, and adds or subtracts one if it's in the range [0-8] or [1-9], respectively. It has three issues:

  1. It's mostly text manipulation, and only looks at the first character, so it doesn't know if a number is negative. This is fixable, but I like the current behavior as-is.
  2. It clobbers the search register: "/, and if you have hlsearch on, then all numbers in the current buffer will be highlighted when you use this. You can append :noh<CR> to each of the above map commands to stop the highlighting, but I don't know how to nicely stop the register from being clobbered.
  3. It works on the first number on the line, not the first number after the cursor.

Also, if it were me, I'd map the above to <leader>a and <leader>x, rather than to CTRL-A/X. This way, you have both behaviors available to you. <leader> is, by default, the backslash key (\).

  • The fact that it disregards negative number is fine, in fact the default behaviour seemed strange to me. I think it looks much better with :noh as well. But the real problem is that it can't use cursor position as a base and thus you can increment only the first digit in the line. – firedev Apr 14 '15 at 8:38
  • This stops at the number 9, and above 10 it increments by 11, not 10 ... I think this is unintentional (ie. a bug)? In addition, you always want to use non-recursive mapping (noremap) and never map. See this page for a more detailed explanation of that. – Martin Tournoij May 4 '15 at 0:01

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.