Finding man pages from Neovim with Telescope and/or mini.pick
Source: Dev.to
I use man pages all the time, but sometimes I do not know exactly what I want to open.
printf is a good example.
There is printf(1) for the shell command, printf(3) for the C library function, and depending on what is installed locally there may be even more entries around it.
I wanted something simple inside Neovim:
-
list local man pages
-
fuzzy search them
-
see the section before opening
open using Neovim’s built-in :Man
So I made a small plugin: man.nvim.
Source:
GitHub: https://github.com/moniquelive/man.nvim
What it does
The plugin starts with the system database:
apropos .
Enter fullscreen mode
Exit fullscreen mode
Then it parses the output into picker items like:
[1 User commands] printf(1) - format and print data
[3 Library calls] printf(3) - formatted output conversion
Enter fullscreen mode
Exit fullscreen mode
The important part is that selecting an entry still delegates to Neovim:
:Man 3 printf
Enter fullscreen mode
Exit fullscreen mode
No custom renderer.
No terminal buffer pretending to be man.
No reimplementing man page navigation.
Just discovery through a picker, opening through :Man.
Why not just use :Man?
:Man is great once I know what I want.
But I often want to explore what is installed locally. I may remember part of the name, or the description, or the section.
With the picker I can type things like:
printf
3 printf
formatted output
Enter fullscreen mode
Exit fullscreen mode
And the item text includes the section label, so it is easy to see if I am about to open the command, library call, file format, etc.
Telescope version
The main branch exposes a Telescope extension.
Setup:
require('telescope').setup()
require('man_nvim').setup()
Enter fullscreen mode
Exit fullscreen mode
Usage:
:Telescope man
Enter fullscreen mode
Exit fullscreen mode
Internally it uses a table finder. Each parsed man page becomes an entry with data like:
{
name = 'printf',
section = '3',
ref = 'printf(3)',
description = 'formatted output conversion',
}
Enter fullscreen mode
Exit fullscreen mode
The display and ordinal use the same searchable text:
'[3 Library calls] printf(3) - formatted output conversion'
Enter fullscreen mode
Exit fullscreen mode
That keeps Telescope doing what Telescope is good at: prompt, sorting, preview, mappings.
mini.pick version
There is also a mini.picker branch using mini.pick.
Setup:
require('mini.pick').setup()
require('man_nvim').setup()
Enter fullscreen mode
Exit fullscreen mode
Usage:
:Pick man
Enter fullscreen mode
Exit fullscreen mode
The interesting part is that most of the plugin is shared conceptually:
-
parse
aproposoutput -
split aliases
-
sort by man section
-
build the
:Mancommand -
show a small preview
-
open the selected page
Only the picker integration changes.
Aliases
Some apropos entries have a lot of aliases.
For example, shells can produce lines like:
builtin(1), !(1), %(1), .(1), :(1), [(1) - shell built-in commands
Enter fullscreen mode
Exit fullscreen mode
The plugin creates searchable items for the aliases, but opens the canonical page.
So if I search for !, I can find !(1), but selecting it opens builtin(1).
That matters because some aliases are not real standalone man pages. They are documented under the first entry in that apropos line.
Filtering noisy results
One thing that got annoying very quickly: Tcl/Tk results.
Sometimes a search like format shows useful results, but also a bunch of Tcl-related entries that I do not care about at that moment.
So I added negative prompt terms.
In both Telescope and mini.pick versions:
format -tcl
format -tcl -tk
Enter fullscreen mode
Exit fullscreen mode
This means:
search for format
exclude entries containing tcl
optionally also exclude tk
The negative terms use smartcase too:
-tcl # excludes tcl, Tcl, TCL, etc when smartcase allows it
-Tcl # case-sensitive exclusion
Enter fullscreen mode
Exit fullscreen mode
The Telescope version implements this as a sorter wrapper. It removes the negative terms from the prompt, filters entries, then delegates the positive query back to Telescope’s normal sorter.
The mini.pick version implements this as a custom source.match. It filters entries first, then passes the positive query back into MiniPick.default_match.
That way I keep each picker’s normal matching behavior instead of replacing it with my own fuzzy matcher.
Opening mappings
The mappings are intentionally boring:
:vertical Man {section} {name}
:Man {section} {name}
:vertical Man {section} {name}
:tab Man {section} {name}
Enter fullscreen mode
Exit fullscreen mode
“ opens vertically by default because that is how I usually want docs next to code.
“ opens with regular :Man for the classic horizontal split habit.
Portability notes
The plugin is meant to work on macOS, Linux, and FreeBSD.
That mostly means avoiding clever assumptions.
apropos output can be noisy. On my macOS machine I saw lines like makewhatis: ... No such file or directory, so the parser simply ignores lines that do not look like man entries.
Sections are also not always just 1 through 9. There can be things like:
3p
3posix
n
Enter fullscreen mode
Exit fullscreen mode
So the parser accepts section names inside parentheses instead of assuming one digit.
For opening pages, the plugin lets Neovim and the system man command handle the hard part.
Installation
With lazy.nvim, for the Telescope version:
{
'moniquelive/man.nvim',
dependencies = { 'nvim-telescope/telescope.nvim' },
config = function()
require('man_nvim').setup()
end,
}
Enter fullscreen mode
Exit fullscreen mode
For the mini.pick version:
{
'moniquelive/man.nvim',
branch = 'mini.picker',
dependencies = { 'echasnovski/mini.pick' },
config = function()
require('mini.pick').setup()
require('man_nvim').setup()
end,
}
Enter fullscreen mode
Exit fullscreen mode
With packer.nvim, for Telescope:
use({
'moniquelive/man.nvim',
requires = { 'nvim-telescope/telescope.nvim' },
config = function()
require('man_nvim').setup()
end,
})
Enter fullscreen mode
Exit fullscreen mode
For mini.pick:
use({
'moniquelive/man.nvim',
branch = 'mini.picker',
requires = { 'echasnovski/mini.pick' },
config = function()
require('mini.pick').setup()
require('man_nvim').setup()
end,
})
Enter fullscreen mode
Exit fullscreen mode
With vim-plug, for Telescope:
Plug 'nvim-telescope/telescope.nvim'
Plug 'moniquelive/man.nvim'
Enter fullscreen mode
Exit fullscreen mode
Then somewhere after plugins load:
require('man_nvim').setup()
Enter fullscreen mode
Exit fullscreen mode
For mini.pick:
Plug 'echasnovski/mini.pick'
Plug 'moniquelive/man.nvim', { 'branch': 'mini.picker' }
Enter fullscreen mode
Exit fullscreen mode
Then:
require('mini.pick').setup()
require('man_nvim').setup()
Enter fullscreen mode
Exit fullscreen mode
Final thoughts
This is a small plugin, but it fits my workflow nicely.
I did not want to replace man.
I only wanted a better way to discover local man pages from inside Neovim.
Telescope and mini.pick both work well for this. The fun part was keeping the core behavior simple, and only changing the picker layer.
If I extend this next, I may add a filesystem fallback for machines where the apropos database is missing or stale.
But for now: fuzzy search local docs, filter away noise, open with :Man.
Profit!