Long, long ago, in a hostel room far, far away, I once read about using Vim as
the pager for
man. It involved using some script which made
vim behave like
less (or something like that). I’d stumbled upon it while trying to make
reading manpages more comfortable, with syntax colouring, navigation, etc.
Of late, with Vim.SE for support, I’ve been customizing Vim more and more.
I’ve made a Git repo of my Vim files, taken baby steps in automating tasks I
often do, and so on. While looking through the recent posts in Unix.SE, I came
across this post which suggested using your editor as the pager. That
kicked up the dusty cobwebs in my decrepit memory module, and I remembered that
old attempt at using Vim for reading manpages. So, I set about trying to make
man’s pager. Why did I submit myself to such cruel and unusual punishment?
- I like Vim.
- I have it customized to my liking.
- It is powerful. The search is way better than anything
lessor your average manpage browser (like
yelp) can offer.
- It can browse to other manpages mentioned using tag navigation (
The post suggested setting
$MANPAGER to a combination of
export MANPAGER="col -b | vim -c 'set ft=man nomod nolist ignorecase' -"
For decidedly non-obvious reasons, it’s not likely to work for you. Why?
man doesn’t support piped commands in
$MANPAGER – BSD’s
does (that’s +1 for you OSX folks). From
MANPAGER, PAGER If $MANPAGER or $PAGER is set ($MANPAGER is used in preference), its value is used as the name of the program used to display the manual page. By default, pager -s is used. The value may be a simple command name or a command with arguments, and may use shell quoting (backslashes, single quotes, or double quotes). It may not use pipes to connect multiple commands; if you need that, use a wrapper script, which may take the file to display either as an argument or on standard input.
I tried the suggested solution (using a wrapper script), which worked fine.
However, it created a problem: I use Git to manage my dotfiles. I’d rather not
rely on stuff outside the repo. Stuff installed by package managers and
differences per distro are a fact of life and have to be handled, but I’d rather
not take pains over what I add to it. One obvious solution is to wrap the
MANPAGER='sh -c "col -b | vim -c \'set ft=man nomod nolist ignorecase\' -"'
Ugly. I also hate having to deal with quoting.
At this point, it struck me: Why should I run this via a pipe? Once Vim starts,
I can perfectly well use
%! col -b to do the job. So:
MANPAGER='vim -c "%! col -b" -c "set ft=man nomod nolist ignorecase" -'
Now, other considerations started popping up. You can easily quite
man), by pressing q, or CtrlC.
Vim usually considers a buffer read from
stdin to be modified. Therefore, to
quit a manpage, you’d have to do
:q!, not just
:q. Thankfully, one of the
options set (
nomod) tells Vim that the buffer hasn’t been modified.
Therefore, we can just use
nnoremap q :q<CR>
Other considerations arise:
- The buffer is modifiable. There’s no reason for it to be so.
- The buffer doesn’t have a name. It would be convenient to see the name of the manpage.
- You don’t want swapfiles hanging around from manpages.
As I pondered over this, I realised that these are settings I’d want to apply to
a manpage no matter how I opened it. Hence, they should really be in Vim’s
filetype settings for
man. So, I created a
1 2 3 4 5 6 7 8 9 10 11 12 function! PrepManPager() if !empty ($MAN_PN) silent %! col -b file $MAN_PN endif setlocal nomodified setlocal nomodifiable setlocal readonly setlocal nolist setlocal noswapfile endfunction autocmd VimEnter * PrepMan()
VimEnter since it runs after any commands specified using
-c are run, so I can get it to run after the filetype has been set.
However, I realised that:
- I wanted to apply some of these settings to manpages irrespective of how they were opened; and
- I’d rather not specify
set ft=manfrom the command line, keeping an eye on using Vim as a general-purpose pager;
VimEnter *felt wrong.
A bit of experimentation later, I found that:
mandoesn’t seem to ever provide a filename as an argument, irrespective of what the manpage says.
MAN_PNto the manpage name (
man(1), for example)
Knowing that I’m reading from
stdin and that
MAN_PN is set (to the manpage
name!), I came up with this version:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 " vimrc if !empty($MAN_PN) autocmd StdinReadPost * set ft=man | file $MAN_PN endif " ftplugin/man.vim setlocal nolist setlocal readonly setlocal buftype=nofile setlocal bufhidden=hide setlocal noswapfile setlocal nomodifiable function! PrepManPager() setlocal modifiable if !empty ($MAN_PN) silent %! col -b -x endif setlocal nomodified setlocal nomodifiable endfunction autocmd BufWinEnter $MAN_PN call PrepManPager() nnoremap q :qa<CR> nnoremap <Space> <PageDown> map <expr> <CR> winnr('$') == 1 ? ':vs<CR><C-]>' : '<C-]>'
export MANPAGER="vim -"
What does this do?
- In the main
vimrc, I check if I’m reading from
MAN_PNis set. If so, set the filetype to
manand the filename to the contents of
- In the filetype-specific setting, use an
autocmdthe relies on the filename being
nomodifiedto tell Vim that the buffer hasn’t been modified, and make it a read-only, non-modifiable, scratch buffer.
- Also, map
:qa, so that I can quit all opened manpages, and Space to Page Down, in keeping with the usual behaviour of
col -b’s use of tabs led to messed up alignment. I had to use
-x(replace tabs with spaces) so that, for example,
man asciishowed up properly.
man man opens up pretty much as I’d like it to.
Why “pretty much”?
MANWIDTH, so I can get a manpage formatted
exactly as wide as I want. If open a manpage within Vim, however (by navigating
the tags, for example), the page is formatted for the full width of Vim. :(
Secondly, Vim leaves this annoying message:
$ man man Vim: Reading from stdin... $
For the moment, I’ve adopted the decidedly un-Vim-like solution of opening a split before navigating to any tags - half the terminal width is fine for me. That’s what the last mapping in the above snippet does: check if I have only one window open, and if so, open a new window before jumping to the tag - with the added benefit using to the thoroughly intuitive (to me) Enter key for jumping to the named page. As a happy side effect, I get to see exactly where I was in the new window! :)
I have no idea how to suppress the
stdin message from Vim itself.
This is my first blog post using Jekyll. Writing it, I have learned quite a bit, which I will write about in another post soon.