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.
This plugin does not have access to
commandline-mode
, cool features like live previews don't work here.If you want a UI to execute buffer local search (like with
/
) use searchbox.nvim.
Make sure you have Neovim v0.5.1 or greater.
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'}
}
}
With paq
'MunifTanjim/nui.nvim';
'VonHeikemen/fine-cmdline.nvim';
The easiest way to use it is calling FineCmdline
... in a keybinding.
Remap Enter
.
- Lua Bindings
vim.api.nvim_set_keymap('n', '<CR>', '<cmd>FineCmdline<CR>', {noremap = true})
If you'd like to remap :
instead.
vim.api.nvim_set_keymap('n', ':', '<cmd>FineCmdline<CR>', {noremap = true})
- Vimscript Bindings
nnoremap <CR> <cmd>FineCmdline<CR>
If you'd like to remap :
instead.
nnoremap : <cmd>FineCmdline<CR>
There is also the possibility to setup a default value before it shows up. Say you want to create a keybinding to use vimgrep
.
<cmd>FineCmdline vimgrep <CR>
FineCmdline
is an alias for the .open()
function in the fine-cmdline
module. So you could also use this.
<cmd>lua require("fine-cmdline").open({default_value = ""})<CR>
The command FineCmdline
does not support ranges, it means that if you call it from visual mode with the range '<,'>
you'll get an error.
If you make a keybinding to call it from visual mode, delete the range first. Like this.
:<C-u>FineCmdline<CR>
If you want to add the range in the input use '<,'>
as the default value.
:<C-u>FineCmdline '<,'><CR>
If you want to change anything from the ui
or add a "hook" you can use .setup()
.
These are the defaults.
require('fine-cmdline').setup({
cmdline = {
enable_keymaps = true,
smart_history = true,
prompt = ': '
},
popup = {
position = {
row = '10%',
col = '50%',
},
size = {
width = '60%',
},
border = {
style = 'rounded',
},
win_options = {
winhighlight = 'Normal:Normal,FloatBorder:FloatBorder',
},
},
hooks = {
before_mount = function(input)
-- code
end,
after_mount = function(input)
-- code
end,
set_keymaps = function(imap, feedkeys)
-- code
end
}
})
-
cmdline.enable_keymaps
, when set to true use the recommended keybindings. If you set tofalse
you will need to do bind the keys in theset_keymaps
hook. -
cmdline.smart_history
, when set to true use the string in the input as a search term, then when you navigate the history only the entries that begin with that term will show up. ImaginePackerSync
is in your command history, just enter the stringPack
and press<Up>
to start looking for it. -
cmdline.prompt
sets the text for the prompt. -
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 receive the instance of the input, so you can do anything with it.
set_keymaps
. Why is this even in a "hook"? Funny story, neovim v0.5 still had bugs and limitations that made things very confusing. This hooks provides helper functions to setup keymaps in a reliable way. If you have neovim v0.7 you can ignore it completely.
If you have neovim v0.7 you should setup your keybindings in the after_mount
, using the vim.keymap.set
function.
after_mount = function(input)
-- make escape go to normal mode
vim.keymap.set('i', '<Esc>', '<cmd>stopinsert<cr>', {buffer = input.bufnr})
end
If you are using neovim v0.6.1 or lower use the set_keymaps
hook.
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.stop_complete_or_previous_item
: If completion menu is visible go to previous item. Else, stop completion without changing the input text. -
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. -
fn.up_search_history
: Take the user input, start a search and show the previous entry that start with that prefix. -
fn.down_search_history
: Take the user input, start a search and show the next entry that start with that prefix.
If you wanted to enable the "smart history" with Alt + k
and Alt + j
, then leave <Up>
and <Down>
to do simple history navigation.
set_keymaps = function(imap, feedkeys)
local fn = require('fine-cmdline').fn
imap('<M-k>', fn.up_search_history)
imap('<M-j>', fn.down_search_history)
imap('<Up>', fn.up_history)
imap('<Down>', fn.down_history)
end
At the moment only this third-party completion engine works inside the floating input:
Other popular completion engines don't provide completions for ex-commands outside the built-in command-line.
If you wish to test your completion engine in fine-cmdline
here is what you need to do:
- Disable the default keybindings.
- Setup your own keybindings.
- Maybe, modify the prompt.
- Perhaps, setup a custom filetype.
Here is a complete example.
local fineline = require('fine-cmdline')
local fn = fineline.fn
fineline.setup({
cmdline = {
-- Prompt can influence the completion engine.
-- Change it to something that works for you
prompt = ': ',
-- Let the user handle the keybindings
enable_keymaps = false
},
popup = {
buf_options = {
-- Setup a special file type if you need to
filetype = 'FineCmdlinePrompt'
}
},
hooks = {
set_keymaps = function(imap, feedkeys)
-- Restore default keybindings...
-- Except for `<Tab>`, that's what everyone uses to autocomplete
imap('<Esc>', fn.close)
imap('<C-c>', fn.close)
imap('<Up>', fn.up_search_history)
imap('<Down>', fn.down_search_history)
end
}
})
This is not a special mode. It's just a normal buffer, incremental search will not work here.
You'll find that most limitations on this plugin are because it can't enable commandline-mode
. When you type in the input your are in insert mode
.
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.
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.
If you find this tool useful and want to support my efforts, buy me a coffee ☕.