The Fine Command Line
Here is the plan: A floating input shows up, you a enter a command and you're done. You can use <Tab>
completion like the regular cmdline
, and can also navigate the command history with <Up>
and <Down>
arrows. That's it.
My hope is that someone else with more knowledge sees this and inspires them to make a Telescope plugin with the same features.
Getting Started
Make sure you have Neovim v0.5.1 or greater.
Dependencies
Installation
Use your favorite plugin manager. For example.
With vim-plug
Plug 'MunifTanjim/nui.nvim'
Plug 'VonHeikemen/fine-cmdline.nvim'
With packer
.
use {
'VonHeikemen/fine-cmdline.nvim',
requires = {
{'MunifTanjim/nui.nvim'}
}
}
Usage
You just need to require this plugin and call .open()
... in a keybinding.
- Lua Bindings
vim.api.nvim_set_keymap(
'n',
'<C-p>',
'<cmd>lua require("fine-cmdline").open()<CR>',
{noremap = true}
)
If you'd like to remap :
instead.
vim.api.nvim_set_keymap(
'n',
':',
'<cmd>lua require("fine-cmdline").open()<CR>',
{noremap = true}
)
- Vimscript Bindings
nnoremap <C-p> <cmd>lua require('fine-cmdline').open()<CR>
If you'd like to remap :
instead.
nnoremap : <cmd>lua require('fine-cmdline').open()<CR>
Configuration
If you want to change anything from the ui
or add a "hook" you can use .setup()
.
This are the defaults.
require('fine-cmdline').setup({
cmdline = {
enable_keymaps = true
},
popup = {
position = {
row = '10%',
col = '50%',
},
size = {
width = '60%',
height = 1
},
border = {
style = 'rounded',
highlight = 'FloatBorder',
},
win_options = {
winhighlight = 'Normal:Normal',
},
},
hooks = {
before_mount = function(input)
-- code
end,
after_mount = function(input)
-- code
end,
set_keymaps = function(imap, feedkeys)
-- code
end
}
})
-
popup
is passed directly tonui.popup
. You can check the valid keys in their documentation: popup.options -
hooks
must be functions. They will be executed during the "lifecycle" of the input.
before_mount
and after_mount
recieve the instance of the input, so you can do anything with it.
A good use case for this would be to change the prompt (do it at your own risk).
require('fine-cmdline').setup({
hooks = {
before_mount = function(input)
-- Beware, the prompt can mess around with the completion
input.input_props.prompt = ':'
end
}
})
set_keymaps
. Why is this even in a "hook"? Funny story, you can only map keys after the input is mounted. And there are other not so funny quirks. So I thought I could make things easier for you.
Setting keymaps
With set_keymaps
you get two parameters. imap
makes non-recursive mappings in insert mode. feedkeys
types keys for you (because of reasons).
Let's say you want to create a shortcut (Alt + s
) for a simple search and replace.
set_keymaps = function(imap, feedkeys)
imap('<M-s>', '%s///gc<Left><Left><Left><Left>')
end
If you need something more complex you can use a function.
set_keymaps = function(imap, feedkeys)
imap('<M-s>', function()
if vim.fn.pumvisible() == 0 then
feedkeys('%s///gc<Left><Left><Left><Left>')
end
end)
end
There are a few utility functions you could use, they are available under .fn
.
local fn = require('fine-cmdline').fn
-
fn.close
: If completion menu is visible, hide it. Else, unmounts the input. -
fn.next_item
: Go to the next item in the completion list. -
fn.previous_item
: Go to the previous item in the completion list. -
fn.complete_or_next_item
: Shows the completion menu if is not visible. Else, navigates to the next item in the completion list. -
fn.up_history
: Replaces the text in the input with the previous entry in the command history. -
fn.down_history
: Replaces the text in the input with the next entry in the command history.
If you wanted to navigate command history with Alt + k
and Alt + j
.
set_keymaps = function(imap, feedkeys)
local fn = require('fine-cmdline').fn
imap('<M-k>', fn.up_history)
imap('<M-j>', fn.down_history)
end
Integration with completion engines
Default keybindings can get in the way of common conventions for completion engines. To work around this there is a way to disable all default keybindings.
cmdline = {
enable_keymaps = false
}
But not all defaults are bad, you can add the ones you like. Here is a complete example.
local fineline = require('fine-cmdline')
local fn = fineline.fn
fineline.setup({
cmdline = {
enable_keymaps = false
},
hooks = {
set_keymaps = function(imap, feedkeys)
imap('<Esc>', fn.close)
imap('<C-c>', fn.close)
imap('<Up>', fn.up_history)
imap('<Down>', fn.down_history)
end
}
})
Caveats
This is not a special mode. It's just a normal buffer, incremental search will not work here.
There is a known issue with cmdwin (the thing that shows up when you press q:
by accident). cmdwin
and fine-cmdline
have the same goal, execute ex-commands. Problem is cmdwin
will be the one executing the command, and you will bump into some weird behavior. If for some reason you're in cmdwin
and call fine-cmdline
, press <C-c>
twice (one to close the input, one to close cmdwin
). Don't try anything else. Just close both.
Contributing
How nice of you. Keep in mind I want to keep this plugin small. Scope creep is the enemy. This thing already does everything I want.
Bug fixes are welcome. Suggestions to improve defaults. Maybe some tweaks to the lua public api.
If you want to improve the ui it will be better if you contribute to nui.nvim.
Support
If you find this tool useful and want to support my efforts, buy me a coffee