Scroll bar for vim(curses-based one, not gvim)?

10

2

As a Linux user, I have been quite comfortable with CLI and TUI tools, but I miss the little scrollbar present in almost every GUI program. It has always been easier for me to know how long the file is and where I am from the scrollbar instead of "9752 lines, 24%".

What I expect is a ASCII scrollbar that looks like

|
|
|
|
#
#
#
|
|
|

and I can configure to appear on the left or right (and if on the left, the relative position to line numbers and fold marks). Is there already a Vim plugin to do this, or how can I write my own one? Vim's plugin framework doesn't seem to support such UI modifications directly.

xiaq

Posted 2011-04-28T07:33:18.607

Reputation: 466

Answers

3

If you are considering the "write your own plugin" route, Vim's 'sign' feature might be a good place to start. This feature is how e.g., syntax checking plugins highlight errors.

vim sign example

A simple approach to place the sign would then be:

  1. Determine where you are in the file as a percentage p
  2. Determine how many lines are visible in the vim windows L
  3. Place a sign at the line number closest to int(p*L)
  4. Recalculate on movement around the file

redacted

Posted 2011-04-28T07:33:18.607

Reputation: 2 400

Thanks! This is very close to my stated requirement, though from the doc it seems that signs can only be drawn on the left side. Good answer though, accepted! – xiaq – 2012-07-28T09:18:20.477

10

It is possible to use the statusline as a scrollbar. I used to have the following in my .vimrc, which emulates a scrollbar (also it is only horizontally, but it works surprisingly well). This was originally discussed on the vim_use Mailinglist some years ago.

func! STL()
  let stl = '%f [%{(&fenc==""?&enc:&fenc).((exists("+bomb") && &bomb)?",B":"")}%M%R%H%W] %y [%l/%L,%v] [%p%%]'
  let barWidth = &columns - 65 " <-- wild guess
  let barWidth = barWidth < 3 ? 3 : barWidth

  if line('$') > 1
    let progress = (line('.')-1) * (barWidth-1) / (line('$')-1)
  else
    let progress = barWidth/2
  endif

  " line + vcol + %
  let pad = strlen(line('$'))-strlen(line('.')) + 3 - strlen(virtcol('.')) + 3 - strlen(line('.')*100/line('$'))
  let bar = repeat(' ',pad).' [%1*%'.barWidth.'.'.barWidth.'('
        \.repeat('-',progress )
        \.'%2*0%1*'
        \.repeat('-',barWidth - progress - 1).'%0*%)%<]'

  return stl.bar
endfun

hi def link User1 DiffAdd
hi def link User2 DiffDelete
set stl=%!STL()

Make sure you have the laststatus option set to 2.

Christian Brabandt

Posted 2011-04-28T07:33:18.607

Reputation: 1 196

How do you set the color of the scroll position marker's BG? It is almost indistinguishable from the rest of the bar using the solarized theme on kde. – Mike – 2015-03-06T02:33:51.743

Guess from the last lines, that the highlighting groups User1 and User2 are used. You can redefine them. – Christian Brabandt – 2015-03-06T05:51:23.623

I really like this solution as it puts the scrollbar in a place where it doesn't take up any space from the coding window. Thanks, Christian! – dotancohen – 2012-07-09T16:05:17.160

I like the idea, and can even perhaps live with a horizontal pseudo-scrollbar. But @redacted presented a solution closer to my stated requirement. have +1 on your answer though. Thanks! – xiaq – 2012-07-28T09:16:01.587

6

My attempt for redemption of my earlier faux pas ....

I liked the idea, so today I wrote a plugin for VIM to show a scrollbar 'thumb' using the signs feature of vim.

It's still VERY beta, but it's usable right now, I still have work to do on it, including typing up all the docs and comments and stuff.

I'll post the source here, but you're welcome to pull it from my Hg Repo. (Don't laugh too hard over the other stuff)

Remember... VERY beta, considering I've never written a plugin before, only dabbling in VimL over the years. (less than 12 hours from concept to working prototype! yay!)

I'll keep working on it, kinda neat. The colors are garish for a reason, easy to see what changes. It has a big bug right now, you can't make the signs all go away by toggling it off. I know how to implement this, just wanted to share.


Pictures are useful:

Vim-Scrollbar in action


VIM Curses Scrollbar - v0.1 - L Nix - lornix@lornix.com Hg Repo

" Vim global plugin to display a curses scrollbar
" Version:      0.1.1
" Last Change:  2012 Jul 06
" Author:       Loni Nix <lornix@lornix.com>
"
" License:      TODO: Have to put something here
"
"
if exists('g:loaded_scrollbar')
    finish
endif
let g:loaded_scrollbar=1
"
" save cpoptions
let s:save_cpoptions=&cpoptions
set cpoptions&vim
"
" some global constants
if !exists('g:scrollbar_thumb')
    let g:scrollbar_thumb='#'
endif
if !exists('g:scrollbar_clear')
    let g:scrollbar_clear='|'
endif
"
"our highlighting scheme
highlight Scrollbar_Clear ctermfg=green ctermbg=black guifg=green guibg=black cterm=none
highlight Scrollbar_Thumb ctermfg=red   ctermbg=black guifg=red   guibg=black cterm=reverse
"
"the signs we're goint to use
exec "sign define sbclear text=".g:scrollbar_clear." texthl=Scrollbar_Clear"
exec "sign define sbthumb text=".g:scrollbar_thumb." texthl=Scrollbar_Thumb"
"
" set up a default mapping to toggle the scrollbar
" but only if user hasn't already done it
if !hasmapto('ToggleScrollbar')
    map <silent> <unique> <leader>sb :call <sid>ToggleScrollbar()<cr>
endif
"
" start out activated or not?
if !exists('s:scrollbar_active')
    let s:scrollbar_active=1
endif
"
function! <sid>ToggleScrollbar()
    if s:scrollbar_active
        let s:scrollbar_active=0
        " clear out the autocmds
        augroup Scrollbar_augroup
            autocmd!
        augroup END
        "call <sid>ZeroSignList()
    else
        let s:scrollbar_active=1
        call <sid>SetupScrollbar()
    endif
endfunction

function! <sid>SetupScrollbar()
    augroup Scrollbar_augroup
        autocmd BufEnter     * :call <sid>showScrollbar()
        autocmd BufWinEnter  * :call <sid>showScrollbar()
        autocmd CursorHold   * :call <sid>showScrollbar()
        autocmd CursorHoldI  * :call <sid>showScrollbar()
        autocmd CursorMoved  * :call <sid>showScrollbar()
        autocmd CursorMovedI * :call <sid>showScrollbar()
        autocmd FocusGained  * :call <sid>showScrollbar()
        autocmd VimResized   * :call <sid>showScrollbar()
    augroup END
    call <sid>showScrollbar()
endfunction
"
function! <sid>showScrollbar()
    " not active, go away
    if s:scrollbar_active==0
        return
    endif
    "
    let bnum=bufnr("%")
    let total_lines=line('$')
    let current_line=line('.')
    let win_height=winheight(0)
    let win_start=line('w0')+0 "curious, this was only one had to be forced
    let clear_top=float2nr((current_line * win_height) / total_lines) - 1
    if clear_top < 0
        let clear_top=0
    elseif clear_top > (win_height - 1)
        let clear_top=win_height - 1
    endif
    let thumb_height=float2nr((win_height * win_height) / total_lines)
    if thumb_height < 1
        let thumb_height=1
    elseif thumb_height > win_height
        let thumb_height=win_height
    endif
    let thumb_height=thumb_height + clear_top
    let linectr=1
    while linectr <= clear_top
        let dest_line=win_start+linectr-1
        exec ":sign place ".dest_line." line=".dest_line." name=sbclear buffer=".bnum
        let linectr=linectr+1
    endwhile
    while linectr <= thumb_height
        let dest_line=win_start+linectr-1
        exec ":sign place ".dest_line." line=".dest_line." name=sbthumb buffer=".bnum
        let linectr=linectr+1
    endwhile
    while linectr <= win_height
        let dest_line=win_start+linectr-1
        exec ":sign place ".dest_line." line=".dest_line." name=sbclear buffer=".bnum
        let linectr=linectr+1
    endwhile
endfunction
"
" fire it all up if we're 'active'
if s:scrollbar_active != 0
    call <sid>SetupScrollbar()
endif
"
" restore cpoptions
let &cpoptions=s:save_cpoptions
unlet s:save_cpoptions
"
" vim: set filetype=vim fileformat=unix expandtab softtabstop=4 shiftwidth=4 tabstop=8:

lornix

Posted 2011-04-28T07:33:18.607

Reputation: 9 633

1Your repo has been removed. It would be great if you could put this up somewhere like github and let others contribute to it. I really think it looks great. – Mike – 2015-02-27T22:38:55.797

This makes Vim very sluggish when I scroll with mouse wheel or even simply with jjjjjjjjjjjj. – Ruslan – 2019-08-19T10:34:51.540

You mean guioptions and as the help clearly states, this only works for the gui version of vim. – Christian Brabandt – 2012-07-06T09:12:34.993

Nice. I have implemented something similar in the DynamicSigns plugin. BTW: note, that signs are not drawn, on folded lines. – Christian Brabandt – 2012-07-08T13:01:21.693

Thank you! I figured I should make up for my foobar earlier, then I got more interested in it... so I wrote it. As always, the initial working is easy... getting it all spiffy and cool-looking is the frustrating part. (no signs on folds... noted) – lornix – 2012-07-08T13:08:11.110

Thanks! But judging from the data @redacted brought up the sign feature earlier than you, so it's perhaps more polite to accept his answer. Have +1'ed on your answer. – xiaq – 2012-07-28T09:17:09.387

0

Not an ideal solution but you can find out where in the file you are either in the status line with something like

set statusline=%<%m\ %f\ %y\ %{&ff}\ \%=\ row:%l\ of\ %L\ col:%c%V\ %P

or using set number to have a line number before each line.

Unless you modified vim source (ncurses) then I do not think this is possible but I could be wrong.

Sardathrion - against SE abuse

Posted 2011-04-28T07:33:18.607

Reputation: 390

Thanks but I already knew this... I was just looking for something easier on the eye. – xiaq – 2011-08-23T05:09:54.767

It was a long shot. – Sardathrion - against SE abuse – 2011-08-23T07:09:03.677

0

Here's a version that is draggable with the mouse. It also updates only when the scrollwheel is being used -- if need a scrollbar, your hand should be on the mouse anyways.

sign define scrollbox texthl=Visual text=[]
fun! ScrollbarGrab()
    if getchar()=="\<leftrelease>" || v:mouse_col!=1
        return|en
    while getchar()!="\<leftrelease>"
        let pos=1+(v:mouse_lnum-line('w0'))*line('$')/winheight(0)
        call cursor(pos,1)
        sign unplace 789
        exe "sign place 789 line=".(pos*winheight(0)/line('$')+line('w0')).b:scrollexpr
    endwhile
endfun
fun! UpdateScrollbox()
    sign unplace 789
    exe "sign place 789 line=".(line('w0')*winheight(0)/line('$')+line('w0')).b:scrollexpr
endfun
fun! ToggleScrollbar()
    if exists('b:opt_scrollbar')
        unlet b:opt_scrollbar
        nun <buffer> <leftmouse>
        iun <buffer> <leftmouse>
        nun <buffer> <scrollwheelup>
        nun <buffer> <scrollwheeldown>
        iun <buffer> <scrollwheelup>
        iun <buffer> <scrollwheeldown>
        exe "sign unplace 789 file=" . expand("%:p")
        exe "sign unplace 788 file=" . expand("%:p")
    el
        let b:opt_scrollbar=1
        nno <silent> <buffer> <leftmouse> <leftmouse>:call ScrollbarGrab()<cr>
        ino <silent> <buffer> <leftmouse> <leftmouse><c-o>:call ScrollbarGrab()<cr>
        nno <buffer> <scrollwheelup> <scrollwheelup>:call UpdateScrollbox()<cr>
        nno <buffer> <scrollwheeldown> <scrollwheeldown>:call UpdateScrollbox()<cr>
        ino <buffer> <scrollwheelup> <scrollwheelup><c-o>:call UpdateScrollbox()<cr>
        ino <buffer> <scrollwheeldown> <scrollwheeldown><c-o>: call UpdateScrollbox()<cr>
        let b:scrollexpr=" name=scrollbox file=".expand("%:p")
        exe "sign place 789 line=".(line('w0')*winheight(0)/line('$')+line('w0')).b:scrollexpr
        exe "sign place 788 line=1".b:scrollexpr
    en
endfun

q335r49

Posted 2011-04-28T07:33:18.607

Reputation: 111

This does work for mouse, but doesn't get updated when you scroll with e.g. Ctrl+F. The marker seems to remain in its original line number. Doing :call UpdateScrollbox() works, but is not user-friendly. Might need hooks on all movement keys, or, better, one hook on a scroll event, if it's possible. – Ruslan – 2019-08-19T10:29:19.380