More formats for number increment/decrementing

I know this is a core vim feature, so forgive me if this is not the right place.

I’ve been using vim and neovim for a few years now and one of the features I use often is the incrementing and decrementing of numbers (<ctrl>a, <ctrl>b and the likes). This works for decimal, octal, binary and hexadecimal numbers and you can even increment incrementally with g<ctrl>a. Through the nrformats option you can enable or disable incrementing hex, bin or other types of numbers.

But it only supports the most common notation of these numbers, which is most noticeable for the hexadecimal numbers.

For example, pressing <ctrl>a on 0x10F will change it to 0x110, but only when is has that leading 0x in front of it.

Some languages, such as (System)Verilog or VHDL, use a different format, which means increment and decrement will not work here.

I’ve been looking for a plugin that can reliably offer that experience for other formats, but they always have some form of side-effect: g<ctrl>a does not work, cursor is moved, registers are overwritten, jumps are inserted etc.

I’ve tried to add support to one plugin (see nextval.vim), but ultimately gave up on it because it was breaking too many other things for me.

So I am wondering what would be the best way for (neo)vim to support uncommon number formats?
Should this be achieved through a plugin or should vim handle this internally?
I don’t think hard-coding multiple formats is a sustainable way.
Perhaps through a more flexible nrformats option which allows you to specify patterns?
Or could treesitter be used to inform neovim that the word under the cursor is a hexadecimal value and should thus be handled that way?

In short I would like to add support for other number formats to the increment/decrement feature but I have no idea where to start or even what the best way to go would be.

I was hoping someone might be willing to provide some insight how to tackle this the best way.

I was thinking of creating an issue on the neovim github, but I am not sure if that is the right place.

1 Like

You may want to post an issue on Vim’s repo and see what the response is. If it’s not something Bram wants added to Vim then perhaps it could be added to Neovim; I’m not sure if this is something the Neovim core team would want to diverge from Vim on, but there’s probably no harm in asking about it.

If this were done within (Neo)Vim itself, then using an option sounds like the way to go. Not sure if it’d be a change directly to nrformats or if a new option would be introduced; fwiw though, to me nrformats sounds like an option that would allow you to specify the formats, so adding additional functionality to that option is what I’d lean towards, probably (unless doing that would break backwards compatibility or cause other issues).

All that being said, this feels like something that could be handled by a (Lua) plugin, and theoretically it wouldn’t be that difficult: just parse the value under the cursor, increment (or decrement) it as requested, and then write that new value to the buffer in the old value’s place. Would you mind sharing/linking some examples of the specific formats you want to have work? I could maybe try my hand at writing a proof-of-concept plugin just to see how hard it would be, and could use those as test cases.

To me, this just sounds like a problem with the plugins’ implementations; for example, iirc nvim-colorizer.lua allows for incrementing/decrementing hex, rgb, etc. values for colors (if not nvim-colorizer itself then whatever the demo uses to increment/decrement the color values), so doing the same for number formats should be both possible and fairly straightforward. I could be wrong though.

2 Likes

Thanks for the reply!

I’ll create an issue over there and see what comes out of it.

When trying to modify a vimscript plugin I felt like I there were too many ‘tricks’ needed and I was wondering if it would not be better if it was a builtin feature.

Sure, in SystemVerilog a number can be defined as 8'hE3. The leading number is the size of the value in bits (and may be omitted), the ' is always there (it is a typecast operation). Then follows the format specification: h for hexadecimal, d for decimal, o for octal and b for binary. The last part is the actual value in the previously mentioned format, a _ can be used as a separator. All letters are case-insensitive.

Some more examples:

'd98
12'h3de
32'hFFFF_FFFF
7'bB011_1101
'b011_1100
'heD21CC

It is valid syntax for the value to be larger then the size in bits, so 1'hAABB would allowed.

The parsing of the numbers itself has a lot of possibilities, but in the end it is quite doable.
It’s mostly the surrounding implementation that I find more complicated, like jumping to the next value in the line, or making sure no registers or marks are overwritten or repeating with ..

I’ve raised the issue with vim: More/flexible formats for nrformats · Issue #7910 · vim/vim · GitHub

1 Like

Using a couple of your provided examples to test I managed to get a proof-of-concept increment implementation working:

function _G.increment_under_cursor()
  local cursor = vim.api.nvim_win_get_cursor(0)
  local text = vim.api.nvim_buf_get_lines(
    0, cursor[1] - 1, cursor[1], true)[1]

  -- Assumes cursor is at start of value
  local full_value = vim.split(text:sub(cursor[2] + 1), " ")[1]
  local target = vim.split(full_value, "'")

  local size = #target > 1 and target[1] or nil
  local type = (#target > 1 and target[2] or target[1]):sub(1, 1)

  local value = (#target > 1 and target[2] or target[1]):sub(2)

  if type == 'h' then
    local incremented = string.format("%x", tonumber(value, 16) + 1)
    vim.api.nvim_buf_set_text(
      0,
      cursor[1] - 1, -- start row
      cursor[2], -- start col
      cursor[1] - 1, -- end row
      cursor[2] + #full_value, -- end col
      { string.format('%s\'%s%s', size or '', type, incremented) }
    )
  elseif type == 'd' then
    -- Handle decimal values
  -- elseif type == 'o' then handle octal nums end etc.
  end
end

-- 'heD21CC
-- 12'h3de

If you want to try it out yourself, just paste that into a file, run :luafile %, position your cursor at the start of one of the commented out values at the bottom of the file, and then run :lua increment_under_cursor(). Seems to work fine for me.

Of course, the code above is by no means perfect or even usable, and would therefore need quite a bit of improvement in order to be viable for a plugin. (One obvious issue is that it only works if you position your cursor at the start of the value.) However, I think it illustrates my point that this sort of thing can be done in a plugin without causing the issues you listed (moving cursor, overwriting registers, etc.). With some time and effort I’m sure someone could make a plugin to provide the functionality you want; if you wanted, you could make an issue on nvim-lua/wishlist asking for such a plugin.

4 Likes

Thanks for putting your time into this!

This certainly looks better than vimscript :slight_smile:.

I think, based on this, I can try to build some plugin myself. Time to learn some lua :slight_smile:.

2 Likes

With your code as starting point I decided to make a plugin from it.
It is still in the early stages, but feedback would be appreciated.

3 Likes