insert mode enhancement plugin for Neovim
API Stability Warning
The API is not yet stable. If you have made any advanced customizations, they may stop working without notice.
CleanShot.2025-06-28.at.18.11.08.mp4
nvim-ix is a plugin for Neovim that provides insert-mode enhancement
functionalities. It internally utilizes the core library nvim-cmp-kit to offer
a user-friendly API.
nvim-ix: The interface for user configuration and operation.nvim-cmp-kit: The core engine responsible for the actual completion logic, such as generating completion candidates and interacting with LSP.
This architecture allows nvim-ix to provide Neovim users with an
easy-to-configure and intuitive experience, while nvim-cmp-kit handles complex
processing, achieving both stability and advanced features.
- Completion: Input completion in insert mode and command-line mode.
- Signature Help: Displays function and method signatures (argument information, etc.).
- Built-in Common Sources:
- Completion
github: GitHub-related completions (ghcommand is required).buffer: Words from the current buffer.path: File and directory paths.calc: Evaluation of simple mathematical expressions.emoji: Emoji characters.cmdline: Neovim commands.lsp.completion: Completion candidates from LSP servers.
- SignatureHelp
lsp.signature_help: Signature help from LSP servers.
- Completion
- Key-mapping:
ix.charmapfor setting up keybindings with reduced conflicts. - Pretty Markdown Rendering: Completion documentation / Signature Help rendering.
Dependencies
- Neovim 0.11 or later.
nvim-ixusesvim.on_keywith empty return string. It's introduced in Neovim 0.11.
- NerdFonts
nvim-ix's default view uses NerdFonts.
Dependencies (Optional)
- icon provider
mini.iconsorlspkind.nvim
Lazy.nvim example
-- lazy.nvim
{
"hrsh7th/nvim-ix",
dependencies = {
"hrsh7th/nvim-cmp-kit",
},
}To use nvim-ix, first call the setup function for initial configuration.
vim.o.winborder = 'rounded' -- (Optional) nvim-ix follows global `winborder` settings to render windows
local ix = require('ix')
-- Setup nvim-ix
ix.setup({
-- Register snippet expand function (optional but recommend to set this).
expand_snippet = function(snippet_body)
-- vim.snippet.expand(snippet) -- for `neovim built-in` users
-- require('luasnip').lsp_expand(snippet) -- for `LuaSnip` users
-- require('snippy').expand_snippet(snippet) -- for `nvim-snippy` users
-- vim.fn["vsnip#anonymous"](snippet_body) -- for `vim-vsnip` users
end,
})
-- Setup keymaps (Using `ix.charmap`; See below).
do
-- common.
ix.charmap.set({ 'i', 'c', 's' }, '<C-d>', ix.action.scroll(0 + 3))
ix.charmap.set({ 'i', 'c', 's' }, '<C-u>', ix.action.scroll(0 - 3))
-- completion.
vim.keymap.set({ 'i', 'c' }, '<C-n>', ix.action.completion.select_next())
vim.keymap.set({ 'i', 'c' }, '<C-p>', ix.action.completion.select_prev())
ix.charmap.set({ 'i', 'c' }, '<C-Space>', ix.action.completion.complete())
ix.charmap.set({ 'i', 'c' }, '<C-e>', ix.action.completion.close())
ix.charmap.set({ 'c' }, '<CR>', ix.action.completion.commit_cmdline())
ix.charmap.set({ 'i' }, '<CR>', ix.action.completion.commit({ select_first = true }))
vim.keymap.set({ 'i' }, '<Down>', ix.action.completion.select_next({ no_insert = true }))
vim.keymap.set({ 'i' }, '<Up>', ix.action.completion.select_prev({ no_insert = true }))
ix.charmap.set({ 'i' }, '<C-y>', ix.action.completion.commit({
select_first = true,
replace = true,
no_snippet = true
}))
-- signature_help.
ix.charmap.set({ 'i', 's' }, '<C-o>', ix.action.signature_help.trigger_or_close())
ix.charmap.set({ 'i', 's' }, '<C-j>', ix.action.signature_help.select_next())
endSnippet Engine Integration:
Specify your snippet engine's expansion function with the expand_snippet
option. If not provided, snippet-related functionalities will be disabled.
Key-mapping with ix.charmap
ix.charmap is a utility for easily setting up key-mappings for the plugin's
main operations. It helps avoid key conflicts with other plugins.
ix.setup({ ... }) reference
The following setup call indicates all default settings.
default configuration
local ix = require('nvim-ix')
ix.setup({
---Expand snippet function.
---@type nil|cmp-kit.completion.ExpandSnippet
expand_snippet = nil,
---Check if macro is executing or not.
---@type fun(): boolean
is_macro_executing = function()
return vim.fn.reg_executing() ~= ''
end,
---Check if macro is recording or not.
---@type fun(): boolean
is_macro_recording = function()
return vim.fn.reg_recording() ~= ''
end,
---Completion configuration.
completion = {
---Enable/disable auto completion.
---@type boolean
auto = true,
---Enable/disable auto documentation.
---@type boolean
auto_docs = true,
---Enable/disable auto select first item in completion menu.
---@type boolean
auto_select_first = false,
---Enable/disable LSP's preselect feature.
---@type boolean
preselect = false,
---Default keyword pattern for completion.
---@type string
default_keyword_pattern = require('cmp-kit.completion.ext.DefaultConfig').default_keyword_pattern,
---Performance related configuration.
---@type cmp-kit.completion.CompletionService.Config.Performance
performance = {
fetching_timeout_ms = 120,
menu_update_throttle_ms = 32,
},
---Resolve LSP's CompletionItemKind to icons.
---@type nil|fun(kind: cmp-kit.kit.LSP.CompletionItemKind): { [1]: string, [2]?: string }?
icon_resolver = (function()
local cache = {}
local CompletionItemKindLookup = {}
for k, v in pairs(LSP.CompletionItemKind) do
CompletionItemKindLookup[v] = k
end
local lspkind = { pcall(require, 'lspkind') }
local mini_icons = { pcall(require, 'mini.icons') }
local function update()
if lspkind[1] then
return
end
lspkind = { pcall(require, 'lspkind') }
if mini_icons[1] then
return
end
mini_icons = { pcall(require, 'mini.icons') }
end
vim.api.nvim_create_autocmd({ 'BufEnter', 'CmdlineEnter' }, {
callback = update,
})
-- mini.icons
---@param kind cmp-kit.kit.LSP.CompletionItemKind
---@return { [1]: string, [2]?: string }?
return function(kind)
kind = kind or LSP.CompletionItemKind.Text
if lspkind[1] then
if not cache[kind] then
cache[kind] = { lspkind[2].symbolic(CompletionItemKindLookup[kind]), ('CmpItemKind' .. CompletionItemKindLookup[kind]) }
end
return cache[kind]
end
if mini_icons[1] then
if not cache[kind] then
cache[kind] = { mini_icons[2].get('lsp', CompletionItemKindLookup[kind]:lower()) }
end
return cache[kind]
end
return { '', '' }
end
end)(),
---LSP related configuration.
---@type { servers?: table<string, ix.source.completion.attach_lsp.ServerConfiguration> }
lsp = {
---Configuration for lsp servers.
---@type table<string, ix.source.completion.attach_lsp.ServerConfiguration>
servers = {},
},
},
---Signature help configuration.
signature_help = {
---Auto trigger signature help.
---@type boolean
auto = true,
},
---Attach services for each per modes.
attach = {
---Insert mode service initialization.
---NOTE: This is an advanced feature and is subject to breaking changes as the API is not yet stable.
---@type fun(): nil
insert_mode = function()
if vim.bo.buftype == 'nofile' then
return
end
do
local service = ix.get_completion_service({ recreate = true })
service:register_source(ix.source.completion.github(), { group = 1 })
service:register_source(ix.source.completion.calc(), { group = 1 })
service:register_source(ix.source.completion.emoji(), { group = 1 })
service:register_source(ix.source.completion.path(), { group = 10 })
ix.source.completion.attach_lsp(service, {
default = {
group = 20,
priority = 1,
},
servers = private.config.completion.lsp.servers,
})
service:register_source(ix.source.completion.buffer(), { group = 30, dedup = true })
end
do
local service = ix.get_signature_help_service({ recreate = true })
ix.source.signature_help.attach_lsp(service)
end
end,
---Cmdline mode service initialization.
---NOTE: This is an advanced feature and is subject to breaking changes as the API is not yet stable.
---@type fun(): nil
cmdline_mode = function()
local service = ix.get_completion_service({ recreate = true })
if vim.tbl_contains({ '/', '?' }, vim.fn.getcmdtype()) then
service:register_source(ix.source.completion.buffer(), { group = 1 })
elseif vim.fn.getcmdtype() == ':' then
service:register_source(ix.source.completion.path(), { group = 1 })
service:register_source(ix.source.completion.cmdline(), { group = 10 })
end
end,
},
})Call nvim-ix actions anywhere
It is possible to call nvim-ix actions without ix.charmap for integrating
some of the your specific workflows.
vim.keymap.set('i', '<CR>', function()
ix.do_action(function(ctx)
ctx.completion.complete()
end)
end)Why is ix.charmap needed?
Keys like <CR> and <Tab> are prone to conflicts as they are used for
multiple functions (e.g., confirming completion, inserting a newline, expanding
a snippet). ix.charmap aims to handle nvim-ix actions for these keys with
higher precedence than other mappings, reducing conflicts and ensuring reliable
behavior.
How can I set up key-mappings without using ix.charmap?
You can use ix.do_action for this.
vim.keymap.set('i', '<C-x><C-o>', function()
ix.do_action(function(ctx)
ctx.completion.complete()
end)
end)Why create a new completion plugin?
nvim-ix was developed based on the experience from existing completion plugins
(like nvim-cmp), aiming for a different architectural approach (adoption of
the core engine nvim-cmp-kit) and an API design more compliant with LSP
specifications.