With regards to this line (full code at the bottom):

let savestatus  = "%{&modified ? '%#StatusUnsaved#[+++]%*' : &modifiable ? '[---] ' : '[ R ]'}"

If the file is modified, I wish to print [+++] with the highlighting face of StatusUnsaved. However with my code, it literally prints %#StatusUnsaved#}[+++]%* in the status line instead of just printing [+++] with the highlighting face applied. I have tried nested %{} but that generates errors.

From reading the help it explains the statusline is just a stringf in which string % substitutions occur. So it's like I need to call a function like RenderStatusLine('%#StatusUnsaved#}[+++]%*'), and return that result. I have searched for a function like that but can't find it.

Here is my full status line code:

function MyStatusLine()
    let prefix      = '%{expand("%:h")}'   "Show file path head
    let prefix      = prefix . '/'
    let prefix      = prefix . '%#StatusLineNC#'   "Change to StatusLineNC highlighting
    let prefix      = prefix . ' %t '      "filename only no path (Tail)
    let prefix      = prefix . '%*'        "Return to default color StatusLine / StatusLineNC
    let savestatus  = "%{&modified ? '%#StatusUnsaved#[+++]%*' : &modifiable ? '[---] ' : '[ R ]'}"
    let sep         = ' %= '               "Left/Right separator
    let fug         = "%{exists('g:loaded_fugitive') ? fugitive#statusline() : ''}"
    let position    = ' %6l'               "Current line number
    let position    = position . ',%-3c'   "Current column number, left aligned 3 characters wide
    let position    = position . ' %P '    " Percentage through the file
    return prefix.savestatus.sep.fug.position
set statusline=%!MyStatusLine()
  • 1
    When using %! the general strategy is to bake the functions into a single string as much as possible instead of using embedded functions
    – Mass
    Apr 11, 2020 at 17:52
  • 1
    I think what @Mass is trying to say is that rather than asking the statusline to get evaluated twice, have your function do the check: let savestatus = &modified ? 'a' : &modifiable ? 'b' : 'c'—notice how it is not a string, but an expression that evaluates to a string when the function is called
    – D. Ben Knoble
    Apr 11, 2020 at 19:33
  • 1
    Thanks @D.BenKnoble, that did the trick! I thought it would result in savestatus getting one value (the first time it is called at initialisation), but it is rev-evaluated every time, great! Apr 11, 2020 at 20:38

2 Answers 2


Since %! statusline functions are re-evaluated (at what frequency, I do not know), you can do this instead:

let savestatus  = &modified ? '%#StatusUnsaved#[+++]%*' : &modifiable ? '[---] ' : '[ R ]'

According to :help statusline:

    When the option starts with "%!" then it is used as an expression,
    evaluated and the result is used as the option value.  Example: >
        :set statusline=%!MyStatusLine()
<   The *g:statusline_winid* variable will be set to the |window-ID| of the
    window that the status line belongs to.
    The result can contain %{} items that will be evaluated too.
    Note that the "%!" expression is evaluated in the context of the
    current window and buffer, while %{} items are evaluated in the
    context of the window that the statusline belongs to.

Therefore, %{} should be evaluated, so it is bizarre that it is not.

Fundamentally, I believe the problem is the same as if you do this:

set statusline=%{&modified?'%1*[+++]%*':&modifiable?'[---]':'[R]'}

Because the %1* "just some string," it doesn't get evaluated.

However, you might be interested in %m and %r flags (and their capital variants). You'll have less control over the exact text, but they just work, and you can color them how you see fit.

  • The only problem is, if I open a help window, then if my other file is modified, it will show the help window as modified too. If I go to the other window and save it, then it changes both the saved and help file windows to not showing modified. I tried setlocal statusline=%!MyStatusLine() but then the statusline is just blank. Apr 11, 2020 at 22:55
  • Hm... I’ll have to investigate a bit.
    – D. Ben Knoble
    Apr 11, 2020 at 23:20
  • I actually started rewriting my status line config so I can have none default %m and %r flags. Thanks for you help though, helped me get there in the end! Apr 13, 2020 at 12:35

With the help of D. Ben Knoble

hi StatusLine           guifg=#000000 guibg=#00CC00 gui=none " WARNING! By default gui set to reverse, need to overide it with none
hi StatusLineNC         guifg=#192224 guibg=#ABABAB gui=none " Status line None current
hi _StatusFileName      guifg=#FFFFFF guibg=#007700 gui=bold
hi _StatusFileNameNC    guifg=#FFFFFF guibg=#777777 gui=bold
hi _StatusModified      guifg=#FFFFFF guibg=#FF0000 gui=BOLD
hi _StatusGit           guifg=#009900 guibg=#00CC00
hi _StatusGitNC         guifg=#777777 guibg=#ABABAB

function MyStatusLine(currentWindow)
    let s = ""
    let s .= "%{&l:modifiable?expand('%:h').'/':''}"    " Show file path head for modifiable files
    let s .= "%{(&readonly||!&modifiable)?'[R]':''}"    " If readonly file, show [R] instead of filepath
    if a:currentWindow
        let s .= "%#_StatusFileName#"                   " Change to StatusFileName highlighting
        let s .= "%#_StatusFileNameNC#"                 " Change to StatusFileNameNC highlighting
    let s .= " %t "                                     " filename only no path (Tail)
    let s .= "%*"                                       " Return to default color StatusLine / StatusLineNC
    let s .= "%#_StatusModified#%{&modified?' +++ ':''}%*"
    let s .= " %= "                                     " Left/Right separator
    if exists('g:loaded_fugitive')
        if a:currentWindow
            let s .= "%*%#_StatusGit#%{&l:modifiable ? fugitive#statusline() : ''}%*"
            let s .= "%*%#_StatusGitNC#%{&l:modifiable ? fugitive#statusline() : ''}%*"
    let s .= " %6l"                                     " Current line number
    let s .= ",%-3c"                                    " Current column number, left aligned 3 characters wide
    let s .= " %P "                                     " Percentage through the file
    return s
"set statusline=%!MyStatusLine('Enter')
autocmd BufWinEnter,WinEnter * setlocal statusline=%!MyStatusLine(1)
autocmd WinLeave * setlocal statusline=%!MyStatusLine(0)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.