Utility library to n^x your work with the nvim api.
Features
Installation
All features maintain familiarity with their underlying base functions. Below is an overview of their differences and extended functionalities.
Based on vim.keymap.set()
vim.keymap.set('n', ';w', "<Cmd>w<CR>")
vim.keymap.set('n', ';q', "<Cmd>w<CR>", { desc = "Close Current Window" })
vim.keymap.set('', "j", "&wrap ? 'gj' : 'j'", { expr = true, silent = true })
vim.keymap.set('', "<Down>", "&wrap ? 'gj' : 'j'", { expr = true, silent = true })
-- Can be written as
nx.map({
{ ";w", "<Cmd>w<CR>" },
{ ";q", "<Cmd>confirm quit<CR>", desc = "Close Current Window" },
{ { "j", "<Down>" }, "&wrap ? 'gj' : 'j'", "", expr = true, silent = true },
})
-- Options like filetype and wk_label are not directly supported by vim.keymap.set
nx.map({
{ "<leader>ts", "<Cmd>set spell!<CR>", desc = "Toggle Spellcheck", wk_label = "Spellcheck" },
{ "<leader>tp", "<Cmd>MarkdownPreviewToggle<CR>", ft = "markdown", desc = "Toggle Markdown Preview" },
})
-- Wrapper options
nx.map({
-- Line Navigation
{ { "j", "<Down>" }, "&wrap ? 'gj' : 'j'", "" },
{ { "k", "<Up>" }, "&wrap ? 'gk' : 'k'", "" },
{ "$", "&wrap ? 'g$' : '$'", "" },
{ "^", "&wrap ? 'g^' : '^'", "" },
-- Enter insert mode at indentation level on empty lines
{ "i", "len(getline('.')) == 0 ? '\"_cc' : 'i'" },
}, { expr = true, silent = true })
})
Differences
- Map single or multiple keymaps
- The only required values are index
[1]
: lhs and[2]
: rhs mode
: defaults to"n"
and instead of being passed as index[1]
it is optional as[3]
ormode
keyopts
: are passed inline instead of in a separate table- additional options:
wk_label
: to create which-key labels that should differ from the key's descriptionft
: to create filetype specific mappings
{<wrapper_opts>}
: to add options to all keymaps within anx.map()
- additional options:
Detailed Examples Toggle visibility...
-
nx.map({ ";q", "<Cmd>confirm quit<CR>", desc = "Close Current Window" }) ---@ ╰── set a single keymap ---@ ╭── or lists of keymaps nx.map({ -- Line Navigation ---@ ╭── multiple lhs { { "j", "<Up>" }, "&wrap ? 'gj' : 'j'", "" }, { { "k", "<Down>" }, "&wrap ? 'gk' : 'k'", "" }, { "$", "&wrap ? 'g$' : '$'", "" }, { "^", "&wrap ? 'g^' : '^'", "" }, -- Indentation { "i", function() return smart_indent "i" end }, { "a", function() return smart_indent "a" end }, { "A", function() return smart_indent "A" end }, }, { expr = true, silent = true }) ---@ ╰── wrapper opts apply options to all entries nx.map({ { "<Esc>", "<Esc>", "i" }, { "<C-c>", "<Cmd>close<CR>", { "i", "x" } }, { "q", "<Cmd>close<CR>", "x" }, ---@ set filetype keymaps ──╮ (in {wrapper_opts} or for single keymaps) }, { buffer = 0, ft = "DressingInput" })
-
Specify a
mode|mode[]
other than"n"
as index[3]
ormode
key (inline or inwrapper_opts
).nx.map({ { "<kEnter>", "<CR>", { "", "!" }, desc = "Enter" } ---@ ^= ╰── or ──╮ { "<kEnter>", "<CR>", desc = "Enter", mode = { "", "!" } } }, { mode = { "", "!" }) -- or in wrapper_opts (here it has to be the `mode` key)
-
Add options to all entries in a list of keymaps - inline keymap options are treated with higher priority and won't be overwritten by wrapper options. As an example, let's compare nx.map for setting neovim/nvim-lspconfig#suggested-configuration.
---@ reference using the default vim.keymap.set local opts = { noremap=true, silent=true } vim.keymap.set('n', '<space>e', vim.diagnostic.open_float, opts) vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, opts) vim.keymap.set('n', ']d', vim.diagnostic.goto_next, opts) vim.keymap.set('n', '<space>q', vim.diagnostic.setloclist, opts) local on_attach = function(client, bufnr) local bufopts = { noremap=true, silent=true, buffer=bufnr } vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, bufopts) vim.keymap.set('n', 'gd', vim.lsp.buf.definition, bufopts) vim.keymap.set('n', 'K', vim.lsp.buf.hover, bufopts) vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, bufopts) vim.keymap.set('n', '<C-k>', vim.lsp.buf.signature_help, bufopts) vim.keymap.set('n', '<space>wa', vim.lsp.buf.add_workspace_folder, bufopts) vim.keymap.set('n', '<space>wr', vim.lsp.buf.remove_workspace_folder, bufopts) vim.keymap.set('n', '<space>wl', function() print(vim.inspect(vim.lsp.buf.list_workspace_folders())) end, bufopts) vim.keymap.set('n', '<space>D', vim.lsp.buf.type_definition, bufopts) vim.keymap.set('n', '<space>rn', vim.lsp.buf.rename, bufopts) vim.keymap.set('n', '<space>ca', vim.lsp.buf.code_action, bufopts) vim.keymap.set('n', 'gr', vim.lsp.buf.references, bufopts) vim.keymap.set('n', '<space>f', function() vim.lsp.buf.format { async = true } end, bufopts) end
---@ nx.map nx.map({ { "<space>e", vim.diagnostic.open_float }, { "[d", vim.diagnostic.goto_prev }, { "]d", vim.diagnostic.goto_next }, { "<space>q", vim.diagnostic.setloclist }, }, { noremap = true, silent = true }) local on_attach = function(client, bufnr) nx.map({ { "gD", vim.lsp.buf.declaration }, { "gd", vim.lsp.buf.definition }, { "K", vim.lsp.buf.hover }, { "gi", vim.lsp.buf.implementation }, { "<C-k>", vim.lsp.buf.signature_help }, { "<space>wa", vim.lsp.buf.add_workspace_folder }, { "<space>wr", vim.lsp.buf.remove_workspace_folder }, { "<space>wl", function() print(vim.inspect(vim.lsp.buf.list_workspace_folders())) end }, { "<space>D", vim.lsp.buf.type_definition }, { "<space>rn", vim.lsp.buf.rename }, { "<space>ca", vim.lsp.buf.code_action }, { "gr", vim.lsp.buf.references }, { "<space>f", function() vim.lsp.buf.format { async = true } end }, }, { noremap = true, silent = true, buffer = bufnr }) end
-
Register which-key labels that should differ from the mappings' description. They can be a
string
literal,"ignore"
(^="which_key_ignore"
), or{ sub_desc = "<pattern>" }
to exclude a pattern of the mappingsdesc
key.E.g., Search
group
🟣 desc labels | 🔵 custom labelsE.g., Toggle
group
🟣 desc labels | 🔵 custom labelsPreserve searchable description
e.g.::Telescope keymaps
The example below uses
wk_label
for a "SnipRun
-keymap-family". It excludes the string"SnipRun"
from being added to every entry on their which-key page.---@ method 1: a custom `wk_label` per key nx.map({ { "<leader>Rc", "<Cmd>SnipClose<CR>", desc = "Close SnipRun", wk_label = "Close" }, { "<leader>Rf", "<Cmd>%SnipRun<CR>", desc = "Run File" }, { "<leader>Ri", "<Cmd>SnipInfo<CR>", desc = "SnipRun Info", wk_label = "Info" }, { "<leader>Rm", "<Cmd>SnipReplMemoryClean<CR>", desc = "SnipRun Clean Memory", wk_label = "Clean Memory" }, { "<leader>Rr", "<Cmd>SnipReset<CR>", desc = "Reset SnipRun", wk_label = "Reset" }, { "<leader>Rx", "<Cmd>SnipTerminate<CR>", desc = "Terminate SnipRun", wk_label = "Terminate" }, { "<leader>R", "<Esc><Cmd>'<,'>SnipRun<CR>", "v", desc = "SnipRun Range", wk_label = "Run Range" }, }) ---@ method 2: use `sub_desc` in `wrapper_opts` to remove `SnipRun` from all entries nx.map({ { "<leader>Rc", "<Cmd>SnipClose<CR>", desc = "Close SnipRun" }, { "<leader>Rf", "<Cmd>%SnipRun<CR>", desc = "Run File" }, { "<leader>Ri", "<Cmd>SnipInfo<CR>", desc = "SnipRun Info" }, { "<leader>Rm", "<Cmd>SnipReplMemoryClean<CR>", desc = "SnipRun Clean Memory" }, { "<leader>Rr", "<Cmd>SnipReset<CR>", desc = "Reset SnipRun" }, { "<leader>Rx", "<Cmd>SnipTerminate<CR>", desc = "Terminate SnipRun" }, { "<leader>RR", "<Esc><Cmd>'<,'>SnipRun<CR>", "v", desc = "SnipRun Range" }, }, { wk_label = { sub_desc = "SnipRun" } })
-
Allow your language server to assist you with hovers, completions, and diagnostics.
function hover field hover cmp diagnostic This requires your runtime environment to be configured to include your plugin directories. An easy way is to have this automated using folke/neodev.nvim.
Based on nvim_set_hl()
nx.hl({
{ "LineNr", fg = "DraculaComment:fg" },
{ "Normal", bg = "DraculaBg:bg" },
{ "BgDarker", bg = palette.bg .. ":#b-15" },
{ "BufferLineSeparatorShadow", fg = "TabLine:bg:#b-10", bg = "Normal:bg" } }
{ { "Directory", "MarkSign" }, link = "DraculaPurple" },
})
Differences
- Set single or multiple highlights
- The only required values are index
[1]
: hl_name andbg|fg|link|…
: value ns_id
: defaults to0
and instead of being passed as index[1]
it is optional as[3]
orns_id
keyvalues
: are passed inline instead of in a separate table- modifiers for values:
:bg|:fg
: to use single values of other highlights as color source instead of linking the whole group.:#b
: to transform the brightness of a color
{<wrapper_opts>}
to add values to all highlights within anx.hl()
Detailed Examples Toggle visibility...
-
nx.hl({ "GitSignsCurrentLineBlame", fg = "Debug:fg", bg = "CursorLine:bg", italic = true }) ---@ ╰── set a single highlight ---@ ╭── or lists of highlights nx.hl({ { "Hex", fg = "#9370DB" }, -- ╮ { "ColorName", fg = "MediumPurple" }, ---@ ├ kinds of values already possible without nx.nvim { "Decimal", fg = 9662683 }, -- ╯ -- { "Winbar", fg = "DraculaComment:fg" }, ---@ ╭────╯ use single values from other highlight groups { "Normal", bg = "DraculaBg:bg" }, ---@ use a color with transformed brightness ──╮ ╭─ darken { "BufferLineSeparatorShadow", fg = "TabLine:bg:#b-10", bg = "Normal:bg" } } ---@ e.g., with hex var ──╮ ╭─ brighten { "BgLight", bg = palette.bg .. ":#b+15" }, ---@ ╭── multiple highlight names { { "Directory", "MarkSign" }, link = "DraculaPurple" }, }, { bold = true, italic = true }) ---@ ╰── wrapper opts apply values to all entries
-
Add values to all entries in a list of highlights - inline values are treated with higher priority and won't be overwritten by wrapper options.
nx.hl({ { "NeoTreeTabActive", bg = "NeoTreeNormal:bg" }, { "NeoTreeTabInactive", fg = "NeoTreeDimText:fg" }, -- ╮ { "NeoTreeTabSeparatorInactive", fg = "TabLine:bg" }, -- ┤ }, { bg = "TabLine:bg" }) ---@ applies `bg` these -- ╯
Based on nvim_create_autocmd()
nx.au({
{ "BufWritePost", pattern = "options.lua", command = "source <afile>", desc = "Execute files on save" },
{ "BufWritePre", command = "call mkdir(expand('<afile>:p:h'), 'p')", desc = "Create non-existent parents" },
})
nx.au({
{ "BufWinLeave", pattern = "*.*", command = "mkview" },
{ "BufWinEnter", pattern = "*.*", command = "silent! loadview" },
}, { create_group = "RememberFolds" })
Differences
- Create single or multiple auto commands
opts
: are passed inline instead of in a separate table- additional options:
create_group
: to create a group and add theautocmd|autocmd[]
to that group
{<wrapper_opts>}
to add values to all autocmds within anx.au()
- additional options:
Detailed Examples Toggle visibility...
-
nx.au({ "FocusGained", pattern = "*.*", command = "checktime", desc = "Check if buffer changed outside of vim" }) ---@ ╰── create a single autocommand ---@ ╭── or lists of autocommands nx.au({ { "BufWinLeave", pattern = "*.*", command = "mkview" }, { "BufWinEnter", pattern = "*.*", command = "silent! loadview" }, }, { pattern = "*.*" }) ---@ ╰── wrapper opts apply values to all entries without them
-
Besides the usual adding to existing autocommand groups using the
group
key, it is possible to create autocommand groups on the fly with thecreate_group
key.nx.au({ "BufWritePre", -- group = "FormatOnSave", ---@ use `group` as usual to add the autocmd to an already existing group create_group = "FormatOnSave", ---@ or create a new group while creating the autocmd callback = function() if next(vim.lsp.get_active_clients({ bufnr = 0 })) == nil then return end vim.lsp.buf.format({ async = false }) end, }) nx.au({ { "BufWinLeave", pattern = "*.*", command = "mkview" }, { "BufWinEnter", pattern = "*.*", command = "silent! loadview" }, ---@ ╭── create an autocommand group in `wrapper_opts` and add all autocommands within this "nx.au()" call }, { create_group = "RememberFolds" })
Based on nvim_create_user_command()
nx.cmd({
"LspFormat",
function() vim.lsp.buf.format({ async = true }) end,
bang = true,
desc = "Fromat the Current Buffer",
})
Differences
- Create single or multiple commands
- The only required values are index
[1]
: name and[2]
: command opts
: are passed inline instead of in a separate table{<wrapper_opts>}
to add values to all commands within anx.cmd()
Detailed Examples Toggle visibility...
-
---@ ╭── create a single command nx.cmd({ "ResetTerminal", function() vim.cmd("set scrollback=1 | sleep 10m | set scrollback=10000") end }) ---@ ╭── or lists of commands nx.cmd({ { "LspFormat", function() vim.lsp.buf.format({ async = true }) end }, { "LspToggleAutoFormat", function(opt) toggle_format_on_save(opt.args) end, nargs = "?" }, { "ToggleBufferDiagnostics", function() toggle_buffer_diags(vim.fn.bufnr()) end }, }, { bang = true }) ---@ ╰── wrapper opts apply options to all entries without them
There is also nx.set
to assign multiple variables or options.
Next to an array of variables/settings, add the scope (vim.g|vim.opt|vim.bo|...
) as a second parameter. If no scope is specified vim.g
is used.
(This features function currently consists of just over 10 lines of code. It's not as extensive or well annotated, but feel free to use it if you like).
Details Toggle visibility...
-
Variables
nx_set({ dracula_italic = 1, dracula_bold = 1, dracula_full_special_attrs_support = 1, dracula_colorterm = 0, }) -- common way: vim.g.dracula_italic = 1 vim.g.dracula_bold = 1 vim.g.dracula_full_special_attrs_support = 1 vim.g.dracula_colorterm = 0
-
Options
nx.set({ -- General clipboard = "unnamedplus", -- use system clipboard mouse = "a", -- allow mouse in all modes showmode = false, -- print vim mode on enter termguicolors = true, -- set term gui colors timeoutlen = 350, -- time to wait for a mapped sequence to complete fillchars__append = [[eob: ,fold: ,foldopen:,foldsep: ,foldclose:›, vert:▏]], listchars__append = [[space:⋅, trail:⋅, eol:↴]], -- Auxiliary files undofile = true, -- enable persistent undo backup = false, -- create a backup file swapfile = false, -- create a swap file -- Command line cmdheight = 0, -- Completion menu pumheight = 14, -- completion popup menu height shortmess__append = "c", -- don't give completion-menu messages -- Gutter number = true, -- show line numbers numberwidth = 3, -- number column width - default "4" relativenumber = true, -- set relative line numbers signcolumn = "yes:2", -- use fixed width signcolumn - prevents text shift when adding signs -- Search hlsearch = true, -- highlight matches in previous search pattern ignorecase = true, -- ignore case in search patterns smartcase = true, -- use smart case -- ... }, vim.opt)
Install "tenxsoydev/nx.nvim"
via your favorite plugin manager.
The only thing left to do then is to import the nx
functions you want to use.
-
Set it once as global variable, so it can be called anywhere in a configuration
-- if using a global variable, make sure it's set where it will be loaded before it's used in another place _G.nx = require("nx") -- use anywhere nx.map({}) nx.au({}) nx.hl({})
-- E.g., when using a plugin manger like lazy, add a high priority require("lazy").setup({ -- ... { "tenxsoydev/nx.nvim", priority = 100, config = function() _G.nx = require "nx" end }, -- ... })
-- or if you prefer not to have the `nx` branding, use another vairable name _G.v = require("nx") -- use anywhere v.map({}) v.au({}) v.hl({})
-
It's also possible to import single modules on demand
local map = require("nx.map") local hl = require("nx.hl") local au = require("nx.au")
To be easily composable, the utilities are written as single modules that can stand on their own. So if they can be helpful within the project you are working - and adding dependencies is too heavy - they are light copy pasta 🍝. In such cases, keeping a small reference of attribution warms the heart of your fellow developer.
There is always room for enhancement. Reach out if you experience any issues, would like to request a feature, or submit improvements. If you would like to tackle open issues - they are usually "help wanted" by nature. Leaving an emoji to show support for an idea that has already been requested also helps to prioritize community needs.