Get "usable" window width in vim script
Asked Answered
A

7

14

get the width of text field in vim

How do I get the width of 3 (marked with green color in the image) in vim script?

If there is no signs column, and there are no other "special columns", I can get it with

winwidth(0) - (max([len(line('$')), &numberwidth-1]) + 1)

Artificial answered 11/10, 2014 at 14:52 Comment(0)
L
10

I think, you should be able to get that width using:

:set virtualedit=all
:norm! g$
:echo virtcol('.')

Alternatively, you could check, whether a signcolumn is present (e.g. using redir)

:redir =>a |exe "sil sign place buffer=".bufnr('')|redir end
:let signlist=split(a, '\n')
:let width=winwidth(0) - ((&number||&relativenumber) ? &numberwidth : 0) - &foldcolumn - (len(signlist) > 1 ? 2 : 0)
Lusatia answered 11/10, 2014 at 19:44 Comment(11)
I'm gonna go with the second one, the first solution will have visual effect, something you don't want in a script. I will not accept your answer, for now, (but thanks :-) to see if someone can come up with something like editareawidth().Artificial
There is no such function and it is not needed.Lusatia
@ChristianBrabandt I would not say it is not needed. It is not needed if all you want buffers for is editing some text, but not if you want to have some interface there. Though just sane scripting interface for signs would be enough.Ligament
@Ligament As an author of several plugins that use the sign feature, I agree, one needs a better VimL integration. I even wrote patches for that. But not for having a editareawidth functionLusatia
Its possible to have &numberwidth non-zero while no number column is being displayed. In the second snippet, one should probably replace &numberwidth with something like ((&l:number || &l:relativenumber) ? &numberwidth : 0).Unaccomplished
&numberwidth is defined as the minimum number of columns to use for the line number. This means that this is useful only if line number < 1000. Or am I missing something here?Anfractuosity
@NotanID you might want to accept the answer. thanks.Lusatia
Shouldn't len(signs) be 1 if the sign column is not present and 2 if it is? that would make the part len(signlist) > 1 ? 2 : 0. At least that's the only thing that works for me. I'm running neovim-nightly v0.5.0-508-gc37d9fa3d, with ale. Redir returns ['--- Signs ---', 'Signs for file.py:'] when the signcolumn is present and only ['--- Signs ---'] when it isn't. On a side note, if/when I start using gitgutter with ale, how would I get the width of signcolumn if it increases beyond 2?Allot
YEs, indeed you are correct here. Note, that nowadays one can use sign_getplaced() to get the signs placed in a buffer. One does not need the redir anymore. I don't understand the comment about signcolumn increasing beyond 2, unless neovim changed that behaviour, the signcolumn can never have more than 2 columnsLusatia
How to just get the current signs width?Jp
@Rainning if signcolumn is enabled, it is always 2 (at least for Vim, Neovim changed this behaviour a bit)Lusatia
D
4

My ingo-library plugin has a ingo#window#dimensions#NetWindowWidth() function for that.

Deterge answered 16/10, 2014 at 10:17 Comment(0)
M
4

Answering because I can't comment yet:

Christian's answer gives the wrong result in the case that the actual number of lines in the file exceeds &numberwidth (because &numberwidth is just a minimum, as kshenoy pointed out). The fix is pretty simple, though, just take the max() of &numberwidth and the number of digits in the last line in the buffer (plus one to account for the padding vim adds):

redir =>a | exe "silent sign place buffer=".bufnr('') | redir end
let signlist = split(a, '\n')
let lineno_cols = max([&numberwidth, strlen(line('$')) + 1])
return winwidth(0)
            \ - &foldcolumn
            \ - ((&number || &relativenumber) ? lineno_cols : 0)
            \ - (len(signlist) > 2 ? 2 : 0)
Margalit answered 28/8, 2018 at 4:10 Comment(0)
D
4

Kale's answer corrected one corner case where the number of lines is exceeding what &numberwidth can display. Here I fix another corner case where the signcolumn option is not set to auto

function! BufWidth()
  let width = winwidth(0)
  let numberwidth = max([&numberwidth, strlen(line('$'))+1])
  let numwidth = (&number || &relativenumber)? numberwidth : 0
  let foldwidth = &foldcolumn

  if &signcolumn == 'yes'
    let signwidth = 2
  elseif &signcolumn == 'auto'
    let signs = execute(printf('sign place buffer=%d', bufnr('')))
    let signs = split(signs, "\n")
    let signwidth = len(signs)>2? 2: 0
  else
    let signwidth = 0
  endif
  return width - numwidth - foldwidth - signwidth
endfunction
Dogooder answered 22/10, 2018 at 1:26 Comment(0)
S
1

None of the above answers take into account the following points -

  • Plugins use sign-groups (if available), so simply running exe "silent sign place buffer=".bufnr('') does not show the sign's placed in the plugin's group

  • Neovim has support for variable signcolumn width

So this is the answer that finally set the ball rolling for me. Of course it is influenced by all of the above answers -

function! BufferWidth()
    let width = winwidth(0)
    let numberwidth = max([&numberwidth, strlen(line('$')) + 1])
    let numwidth = (&number || &relativenumber) ? numberwidth : 0
    let foldwidth = &foldcolumn

    if &signcolumn == 'yes'
        let signwidth = 2
    elseif &signcolumn =~ 'yes'
        let signwidth = &signcolumn
        let signwidth = split(signwidth, ':')[1]
        let signwidth *= 2  " each signcolumn is 2-char wide
    elseif &signcolumn == 'auto'
        let supports_sign_groups = has('nvim-0.4.2') || has('patch-8.1.614')
        let signlist = execute(printf('sign place ' . (supports_sign_groups ? 'group=* ' : '') . 'buffer=%d', bufnr('')))
        let signlist = split(signlist, "\n")
        let signwidth = len(signlist) > 2 ? 2 : 0
    elseif &signcolumn =~ 'auto'
        let signwidth = 0
        if len(sign_getplaced(bufnr(),{'group':'*'})[0].signs)
            let signwidth = 0
            for l:sign in sign_getplaced(bufnr(),{'group':'*'})[0].signs
                let lnum = l:sign.lnum
                let signs = len(sign_getplaced(bufnr(),{'group':'*', 'lnum':lnum})[0].signs)
                let signwidth = (signs > signwidth ? signs : signwidth)
            endfor
        endif
        let signwidth *= 2   " each signcolumn is 2-char wide
    else
        let signwidth = 0
    endif

    return width - numwidth - foldwidth - signwidth
endfunction

Silassilastic answered 20/11, 2020 at 17:9 Comment(0)
G
1

Starting from Vim 8.2.3627, getwininfo()'s output has a textoff containing the

number of columns occupied by any 'foldcolumn', 'signcolumn' and line number in front of the text

therefore subtracting that to the width entry, e.g. computing

getwininfo(win_getid()).width - getwininfo(win_getid()).textoff

should give the desired result.


Before textoff was available, it seems to me that the followinig computation does cut it:

let textoff = max([&numberwidth, (&number ? len(line('$')) + 1 : (&relativenumber ? winfo.height + 1 : 0))])
    \ + &foldcolumn
    \ + (empty(sign_getplaced(bufname(), {'group': '*'})[0].signs) ? 0 : 2)

I made use of both solutions in this plugin of mine for showing soft-wrapped lines.

Granicus answered 14/12, 2022 at 6:13 Comment(0)
S
0

Based on the accepted answer (@Christian Brabandt), for Vims not having textoff I came up with the following function, which I think is simple/reliable/safe enough despite temporarily messing up the window state a little bit:

" Returns the effective number of columns available for text editing                    
" in the current window.                                                                
"                                                                                       
" Apparently on Vim >= 8.2.3627 this should be equivalent:                                 
"                                                                                       
"   getwininfo(win_getid()).width - getwininfo(win_getid()).textoff                     
"                                                                                       
function! EffectiveWindowWidth()                                                        
  let prev_ve = &virtualedit    " Save current value of virtualedit                     
  let &virtualedit = 'all'      " Allow moving cursor beyond line end                   
  let cur_pos = getpos('.')     " Save current cursor position                          
  " In case the cursor is on a wrapped line, go first to the first column,              
  " then go to last window column:                                                      
  exec "norm! 0g$"                                                                      
  let width = virtcol('.')      " Get the column number                                 
  call setpos('.', cur_pos)     " Restore cursor position                               
  let &virtualedit = prev_ve    " Restore original value of virtualedit                 
  return width                                                                          
endfunction
Speciosity answered 17/8 at 3:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.