How can I call a function in vimscript in the context of a specified buffer?

For instance, line2byte works for the current buffer. I am implementing a callback, where I receive a buffer number as an argument and I need to call line2byte for the specified buffer, which might not be the one currently focused by the user.


You can temporarily change the buffer before calling line2byte. However, you need to be careful. In particular, you should avoid changing the alternate file and prevent autocommands from firing. For this, you need keepalt and noautocmd, respectively.

Something like this should work, where I assume the lnum is supplied as an argument as well (untested):

function! Line2ByteBuf(bufnr, lnum) abort
  let l:bufnr = bufnr('')

  execute 'noautocmd keepalt buffer' a:bufnr
  let l:bytes = line2byte(a:lnum)
  execute 'noautocmd keepalt buffer' l:bufnr

  return l:bytes

Note that you should probably also add a check if the supplied buffer bufnr exists and similar. I left that as an exercise for the reader.

  • Nice. I didn't know about noautocmd. Dec 14 '17 at 9:11
  • BTW, IIRC, it should be bufnr('%'). Dec 14 '17 at 9:13
  • Thanks, Luc! I've fixed the bufnr error. Note, bufnr('') and bufnr('%') is equivalent, so I chose the shortest form. Dec 15 '17 at 11:15
  • I didn't know about bufnr('') either. Thanks :) Dec 15 '17 at 13:38

If your function does not take a bufnr as parameter and if there is no alternative function that does, you'll need to jump to that particular buffer and call your function.

The caveat? It'll trigger autocommands, which means stuff you may not desire is likely to happen. EDIT: I didn't know about noautocmd which prevents autocommands from being triggered when loading other buffers momentarily. See @Karl Yngve Lervåg's answer.

In your particular case though, it seems that line2byte(lnum+1) is almost identical to eval(join(map(getbufline(bufnr, 1, lnum), 'strlen(v:val)'), '+'))+lnum+1.

If the line ending is not unix, the offset may be &ff =='unix' ? lnum+1 : 2*lnum+2 -- I haven't checked dos case, nor mixed line endings.

Regarding the second question: "how to emulate byte2line()?", it can be done this way:

function! bufbyte2line(bufid, byteidx) abort
    let lines = map(getbufline(a:bufid, 1, '$'), 'v:val."\n"')
    let line_lengths = map(lines, 'len(v:val)')
    let acc = [0]
    call map(line_lengths, 'add(acc, acc[-1]+v:val)')
    return len(filter(acc, 'v:val <= a:byteidx'))
  • Do you also have such a version for the opposite function: byte2line?
    – languitar
    Dec 14 '17 at 7:59
  • I guess we should be able to play with map() + maybe add() to accumulate bytes and filter() with a threshold to keep the right number of lines which (this number) should be your result. I'll see what I can do for the sake of the exercise. But honestly, I didn't know about noautocmd, you'd better go along with @Karl's solution. Dec 14 '17 at 9:18

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.