Getting the value of the character under the cursor

Posted by Derek Wyatt on July 27, 2015
Getting the ascii / hex code of the character under the cursor in Vim is harder than I thought...

I've been playing with lightline for Vim and wanted to write a custom function for returning the decimal and hex value of the character under the cursor. It turns out that this is non-trivial.

  • The simplest way to get it is to just show it on the statusline (or equivalently in lightline) with %b and %B, as visible in the docs or in this Vim Tips entry.
    • The problem is that I can't get the value into a Vim variable this way.
  • Another way is to simply yank the damn thing with something like "zyl, which will put the value in the @z register, as described here.
    • If virtualedit is set to anything, then this will return a space if there is no character under the cursor, for any mode in which virtualedit is active.
    • What's more is that I may very well not want to run a normal command like this, especially in visual mode.
  • Another way is to do something like let ch = getline('.')[col('.') - 1], which is also described here.
    • This doesn't respect unicode characters because their length is "interesting".

So here's what I eventually tried:

function! LightLineCharacter()
  " Save what's in the `z` register and clear it
  let x = @z
  let @z = ""

  " Redirect output to the `z` register
  redir @z

  " Run the `ascii` command to get all of the interesting character information
  silent! ascii
  redir END

  " Clean up the output and split the line
  let line = substitute(substitute(@z, '^.*> ', '', ''), ',', '', 'g')
  let list = split(line)

  " Reset the `z` register
  let @z = x

  " `dec` and `hex` hold the values I want
  let dec = 0
  let hex = 0

  " If we've split something reasonable, then get decimal and hex values
  if len(list) >= 4
    let dec = list[0]
    let hex = list[2]
  endif

  " Return it the way I want
  return dec . "/0x" . hex
endfunction

However, as I was researching the links for this post, I found the "real" answer:

let dec = char2nr(matchstr(getline('.'), '\%' . col('.') . 'c.'))

You can find the explanation in one of the comments in the StackOverflow post but I'll also explain it here.

  • getline('.') returns the entire line that cursor is sitting on.
  • col('.') returns the column number that the cursor is sitting on.
  • The regular expression \%nc matches a specific given column where n is that column.
  • The '.' regular expression matches exactly one character.

So, let's assume that the column the cursor is currently on is 31. Then the above code evaluates to:

let dec = char2nr(matchstr(getline('.'), '\%31c.'))

which means that you're going to match the single character (multi-byte or otherwise) at position 31 in the string returned by getline('.'). And char2nr() is going to transform the character to a decimal number.

It's really not always that easy to figure out what needs writing when you're in Vimscript!