Is your feature request about something that is currently impossible or hard to do? Please describe the problem.
It is not easy to get the index of the first screen cell of a file position, like the cursor, or a visual mark. This information is necessary when we intend to use it later in a regex with a \%v atom.
virtcol() gives the index of the last screen cell, if 'virtualedit' is empty (which it is by default). This is problematic if we are on a character which occupies several screen cells, like a tab.
vim9script setline(1, "the\tquick\tbrown\tfox") norm! 22| var vcol: number = virtcol('.') norm! 0 search('\%' .. vcol .. 'v')
the cursor has not been repositioned right before "fox"
✘
Notice that when virtcol('.') was evaluated and saved into a variable, the cursor was on the tab right before "fox". Also notice that search() failed to restore the cursor position after :norm! 0. That's because \%v expects the index of the first screen cell of a character, not the last one.
Describe the solution you'd like
A new optional boolean argument for virtcol() ({firstcell}), which – when true – would make virtcol() give the index of the first cell instead of the last one. And which would be immune to 'virtualedit'. That is, if we're on the middle of a tab character while 'virtualedit' has the value all, virtcol('.', true) would still give the index of the first screen cell of the tab.
Describe alternatives you've considered
Alternatively, we can use this expression:
virtcol([line('.'), getline('.')->byteidx(charcol('.') - 2) + 1]) + 1
Which works as expected when used in the previous snippet:
vim9script setline(1, "the\tquick\tbrown\tfox") norm! 22| var vcol: number = virtcol([line('.'), getline('.')->byteidx(charcol('.') - 2) + 1]) + 1 norm! 0 search('\%' .. vcol .. 'v')
the cursor *has* been repositioned right before "fox"
✔
But:
Additional context
The previous example is a bit contrived. We don't really need a regex, nor a \%v atom. We could restore the cursor position with a :norm command:
exe 'norm! ' .. vcol .. '|'
But there are times where we really need a regex and a \%v atom. In those cases, virtcol() is not easy enough to use.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub, or unsubscribe.![]()
Alternatively, we can use this expression:
virtcol([line('.'), getline('.')->byteidx(charcol('.') - 2) + 1]) + 1
It can also be tested and compared to virtcol('.') like this:
vim9script setline(1, "a\tb\tc") for i in range(5) echom virtcol('.') norm! l endfor
1
8
9
16
17
Those are the indexes of the last screen cells of the five characters on the line, which won't match anything with \%v.
vim9script setline(1, "a\tb\tc") for i in range(5) echom virtcol([line('.'), getline('.')->byteidx(charcol('.') - 2) + 1]) + 1 norm! l endfor
1
2
9
10
17
Those are the indexes of the first screen cells of the five characters on the line, which will match all the characters on the line when used in a \%v atom.
Those are the indexes of the last screen cells of the five characters on the line, which won't match anything with %v.
The actual values depend on 'tabstop'. Also, some of those values will match a character with \%v; namely 1, 9 and 17. That's because those screen cells are occupied by characters which only need 1 screen cell; IOW, the first and last screen cells of these characters are the same. But the values 8 and 16 won't match anything with \%v.
BTW, I use the term "screen cell", but I'm not sure it's correct. The help at :h virtcol() uses "screen column"; but in other locations, it also uses "display cell" or "screen cell". Not sure whether there are differences between all of them.
Closing because there is a simple workaround. Instead of writing this:
'\%' .. virtcol('.') .. 'v.'
We can write this:
'.\%' .. (virtcol('.') + 1) .. 'v'
Example:
vim9script setline(1, "the\tquick\tbrown\tfox") norm! 22| var vcol: number = virtcol('.') norm! 0
search('.\%' .. (vcol + 1) .. 'v')
Closed #7964.
Ah no, I re-open because there are other cases where this workaround can't work, and we really need the index of the first screen cell. Here is one – real – example:
vim9script com -bar -range=% RemoveTabs RemoveTabs(<line1>, <line2>) def RemoveTabs(line1: number, line2: number) var view: dict<number> = winsaveview() var mods: string = 'sil keepj keepp' var range: string = ':' .. line1 .. ',' .. line2 var pat: string = '\t' RemoveTabsRep = (): string => synstack('.', col('.')) ->mapnew((_, v: number): string => synIDattr(v, 'name')) ->match('heredoc') >= 0 ? "\t" : repeat(' ', strdisplaywidth("\t", VirtcolFirstCell('.') - 1)) for i in [1, 2] exe mods .. ' ' .. range .. 's/' .. pat .. '/\=RemoveTabsRep()/ge' endfor winrestview(view) enddef var RemoveTabsRep: func def VirtcolFirstCell(filepos: string): number var lnum: number = line(filepos) var col: number = getline(filepos)->byteidx(charcol(filepos) - 2) + 1 return virtcol([lnum, col]) + 1 enddef
This installs a custom Ex command :RemoveTabs whose purpose is to replace all the tabs inside an arbitrary range with spaces. The command must not break any possible alignment. So, if a tab occupies 3 screen cells, it should be replaced with 3 spaces. If another tab occupies 7 screen cells, then it should be replaced with 7 spaces.
Notice that – for it to work correctly – it needs the custom function VirtcolFirstCell(). This is something which I need in quite a few places. So, I've turned it into an exportable function which I import whenever necessary. Still, I think it would be better if Vim could give the information with a builtin function. This way, this line:
: repeat(' ', strdisplaywidth("\t", VirtcolFirstCell('.') - 1))
^-------------------^
Could be rewritten like this:
: repeat(' ', strdisplaywidth("\t", virtcol('.', true) - 1))
^----------------^
And we could get rid of VirtcolFirstCell(), as well as an import in every script where we need this kind of information.
Reopened #7964.
The internal function getvvcol() already does this, it returns the start, cursor and end position.
We can add an argument that specifies one of these.
Closed #7964 as completed via 0f7a3e1.
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()