Reputation: 3115
I've developed a nice customisable statusline in Vim however I'm having real trouble displaying the current mode I'm working in within it.
I use a dictionary to dictate what options to display via the left and right of the statusline:
" Display Options {{{
let s:statusline_options = {
\ 'active': {
\ 'left': [ 'readonly', 'mode' , 'git' ],
\ 'right': [ 'time', 'project' ],
\ },
\ 'components': {
\ 'readonly': 'Statusline_readonly()',
\ 'mode': 'Statusline_mode()',
\ 'git': 'Statusline_git()',
\ 'time': "strftime(%a\ %d\ %b\ %H:%M)",
\ 'project': 'Statusline_project()'
\ },
\ 'seperators': {
\ 'readonly': ' %s',
\ 'mode': '%s >',
\ 'git': ' %s',
\ 'time': ' < ',
\ 'project': '[%s] '
\ },
\ 'components_to_color': {
\ 'mode': 1,
\ 'project': 1
\ },
\ 'theme_colors': {
\ 'default': [ '#abb2bf', '#61afef', '#98c379' ],
\ 'onedark': [ '#abb2bf', '#61afef', '#98c379' ],
\ 'materialbox': [ '#1d272b', '#fb8c00', '#43a047']
\ },
\ 'mode_map': {
\ 'n': 'NORMAL', 'i': 'INSERT', 'R': 'REPLACE', 'v': 'VISUAL', 'V': 'VISUAL', "\<C-v>": 'V-BLOCK',
\ 'c': 'COMMAND', 's': 'SELECT', 'S': 'S-LINE', "\<C-s>": 'S-BLOCK', 't': 'TERMINAL'
\ },
\ }
" }}}
I then piece together the creation of the statusline by introducing color groups and separators and calling the functions which the user specified as Components in the dictionary above:
" Statusline Functions {{{
function! StatuslineComponents(side) abort
let output = ''
" Fetch the components in the statusline dictionary
for v in Fetch('active', a:side)
let method = split(Fetch('components', v), '(')
let component_color = ''
" Check if the item should be specifically coloured
if len(method) > 1 && method[1] != ')'
let output .= StatuslineColor(v) . StatuslineFormat(v, method[0], method[1])
else
let output .= StatuslineColor(v) . StatuslineFormat(v, method[0])
endif
endfor
return output
endfunction
function! StatuslineColor(component)
for v in keys(s:statusline_options.components_to_color)
if v == a:component
return '%#Component_' . v . '#'
endif
endfor
return '%#ComponentDefault#'
endfunction
function! StatuslineFormat(...)
let output = ''
let seperator = Fetch('seperators', a:1)
if a:0 > 2
for param in split(a:3, ',')
let value = call(a:2, [ param ], {})
endfor
else
let value = call(a:2, [], {})
endif
" Remove any last )'s from the value
let value = substitute(value, ')\+$', '', '')
if seperator =~ '%s'
let output = printf(seperator, value)
else
let output = value . seperator
endif
return output
endfunction
function! ChangeStatuslineColor() abort
let s:mode_colors = []
try
for mode_color in Fetch('theme_colors', g:colors_name)
let s:mode_colors += [mode_color]
endfor
catch
try
for mode_color in Fetch('theme_colors', 'default')
let s:mode_colors += [mode_color]
endfor
catch
let s:mode_colors = ['#e06c75'] + ['#e06c75'] + ['#e06c75']
endtry
endtry
if (mode() ==# 'i')
exec printf('hi ComponentDefault guifg=%s', s:mode_colors[1])
elseif (mode() =~# '\v(v|V)')
exec printf('hi ComponentDefault guifg=%s', s:mode_colors[2])
else
exec printf('hi ComponentDefault guifg=%s', s:mode_colors[0])
endif
for component in keys(s:statusline_options.components_to_color)
if (mode() ==# 'i')
exec printf('hi Component_%s guifg=%s', component, s:mode_colors[1])
elseif (mode() =~# '\v(v|V)')
exec printf('hi Component_%s guifg=%s', component, s:mode_colors[2])
else
exec printf('hi Component_%s guifg=%s', component, s:mode_colors[0])
endif
endfor
return ''
endfunction
" Fetch a value from the Statusline options
function! Fetch(key, value) abort
return get(s:statusline_options[a:key], a:value, '')
endfunction
" }}}
My custom functions to be included in the Statusline, along with the key Statusline_mode() function are written below:
" Component Functions {{{
" Show the mode in the statuslin
function! Statusline_mode() abort
return get(s:statusline_options.mode_map, mode(), '')
endfunction
function! Statusline_project() abort
return GetCurrentSite(g:code_dir)
endfunction
function! Statusline_git() abort
let git = fugitive#head()
if git != ''
return ' '.fugitive#head()
else
return ''
endif
endfunction
function! Statusline_readonly() abort
if &readonly || !&modifiable
return ' '
else
return ''
endif
endfunction
" }}}
The statusline is then set via the command group below:
" Set The Statusline {{{
augroup statusline
hi StatusLine guibg=NONE gui=NONE
autocmd!
autocmd WinEnter,BufWinEnter,FileType,ColorScheme,SessionLoadPost
\ * let &l:statusline = StatuslineComponents('left') . '%=' . StatuslineComponents('right') |
\ call ChangeStatuslineColor()
augroup end
" }}}
I have tried using InsertEnter
command group and that does not seem to do the trick.
Upvotes: 1
Views: 8092
Reputation: 172570
Let's start with your more minimalistic case:
let &l:statusline = mode() . ' ' . StatuslineComponents('left') . '%=' . StatuslineComponents('right')
This invokes mode()
once, when defining the 'statusline'
; it never updates, and the value is stuck at n
.
To fix this, you need to use the %{...}
item to evaluate the expression whenever the statusline is updated:
let &l:statusline = '%{mode()} ' . StatuslineComponents('left') . '%=' . StatuslineComponents('right')
Now back to your complete case. It's the same problem:
autocmd WinEnter,BufWinEnter,FileType,ColorScheme,SessionLoadPost
\ * let &l:statusline = StatuslineComponents('left') . '%=' . StatuslineComponents('right') |
\ call ChangeStatuslineColor()
Although you update on certain :autocmd
events, none are triggered on mode change, so you always see the same mode. In any case, as :help mode()
tells you, the correct value can only be obtained from the mentioned statusline expression.
As a first step to fix this, I would drop the :autocmd
altogether and put everything in a statusline expression:
let &statusline = "%{StatuslineComponents('left')}%=%{StatuslineComponents('right')}"
If you run into performance issues, I would extract the slow elements (but not the mode()
call!) into window-local variables, reference them from the statusline expression, and update them with :autocmd
.
autocmd WinEnter,BufWinEnter,FileType,ColorScheme,SessionLoadPost * let w:left = StatuslineComponents('left')
let &statusline = "%{w:left}%=%{StatuslineComponents('right')}"
Upvotes: 6