2

I want to show the git status in the vim statusline, but I can't find any advice on how to do that.

The output of git status -s would be fine, or alternatively a single field readout indicating whether there are any outstanding changes, like this: [Clean] or [Changes]

It would probably be sensible to refresh the display whenever the current buffer was saved to disk.

  • Welcome to Vi and Vim! If you use fugitive, you could always use :G; it also provides fugitive#statusline which is slightly more than just the branch info. There are plugins that show information in the status column (git gutterline or some such). Lastly, you could do something like systemlist('git status -s') and use the results. I'll leave it to somebody with better design thoughts to put the pieces together, though – D. Ben Knoble Aug 2 at 15:31
2

The easiest way would be to call git inside %{} like so:

let &statusline = '%{substitute(system("git status -s"), "\n", " ", "g")}'

Anything inside %{} get evaluated as an expression, and here we call git status -s with system() and replace newlines with a space, resulting in a statusline like:

## repl  M db.go ?? zxc

There's a huge problem with this approach though: it gets run on every screen update, meaning you're forever running git commands. A better approach would be to only run the git command on certain strategic events, capture the result in a variable, and display the variable.

augroup gitstatusline
    au!

    autocmd BufEnter,FocusGained,BufWritePost *
        \ let b:git_status = substitute(system("git status -s"), "\n", " ", "g")
augroup end

let &statusline = '%{get(b:, "git_status", "")}'

So here we record the git status in a buffer-local b:git_status variable, which gets updated when we enter the buffer. This is displayed with get() for safety in case it's not set yet.

You can add some other events as well if you want more frequent updates, such as CursorHold. You can see :help autocmd-events-abc for a list of all supported events.


There is still a problem with this: the command always runs in the current directory, rather than in the buffer's directory. So :e ../other-project/file will give you the wrong results. This may not be an issue for everyone, but to fix that you can use something like:

autocmd BufEnter,FocusGained,BufWritePost *
    \ let b:git_status = substitute(
        \ system(printf("cd %s && git status -s", expand('%:p:h:S'))),
        \ "\n", " ", "g")

You can probably also improve on the formatting of this message; displaying all of git status -s in the statusline probably isn't always the most convenient as it can get rather long, but that's what you asked for :-) The easiest way to check if a directory is clean or not is probably checking if the output of git status --porcelain is blank:

augroup gitstatusline
    au!

    autocmd BufEnter,FocusGained,BufWritePost *
        \ let b:git_clean = system(printf("cd %s && git status --porcelain 2>/dev/null", expand('%:p:h:S'))) is# ''
augroup end

let &statusline = '%{get(b:, "git_clean", "") ? "[clean]" : "[changed]"}'

But you can use any combination of commands and/or parse the output of the git commands in VimScript. you could display [branchanme: 2 changed, 5 new, 6 deleted] as well, but I'll leave building a command for that as an exercise for the reader :-)


As mentioned in the comments, you can also use fugitive which includes some helpful helper functions which do pretty much the same as the above. But if you're looking for a simple "no-plugin" approach to include the output of git commands (or any other shell command or expensive function) then this is the general approach.

| improve this answer | |
  • Awesome answer, look forward to trying it. I find Vim script impenetrable myself. There is no love so true as the programmer's love for punctuation marks. The expand('%:p:h:S') looks like pure magic to me. – Philip Aug 2 at 19:04
  • Hehe, yeah, VimScript is a bit, eh, idiosyncratic @Philip; but I find it's actually not that hard in general. expand() can he used to expand some special values: % refers to the filename of the current buffer. You can apply modifications to that with :<x>, in this case :p to get the full path (foo.vim/home/x/foo.vim), :h to get the head (aka dirname, /home/x), and :S to shell quote it. It looks a bit "magic", but it's certainly a lot more concise than shell_quote(dirname(realpath(get_filename()))) or something like that :-) – Martin Tournoij Aug 2 at 19:26
  • 1
    You don’t need cd if you use git -C – D. Ben Knoble Aug 2 at 19:32
  • @Martin Tournoij One day scientists will research the effect of punctuation marks on the dopaminergic system and sociologists will start speculating how the world might have been if it had been Dickens who had invented the Lambda Calculus, not Alonso Church. Anyhow, this answer does work, though I had to change " to ' and remove spaces in the call to get(). I also added a 2>/dev/null after the git status --porcelain, so that it doesn't interpret error messages as a true value - so that in a non-git-controlled directory, it doesn't say [changed]. – Philip Aug 2 at 19:44
  • I think you used set statusline instead of let &statusline @Philip? The syntax for the 2 is a bit different; let & is a bit more flexible than set with regards to spaces and the like. And yeah, excluding stderr is better, I'll add it to the answer 👍 – Martin Tournoij Aug 2 at 19:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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