tenxsoydev/nx.nvim

github github
utility
stars 25
issues 5
subscribers 2
forks 1
CREATED

2023-02-14

UPDATED

last year


nx.nvim

Utility library to n^x your work with the nvim api.

Features

Installation

Features

All features maintain familiarity with their underlying base functions. Below is an overview of their differences and extended functionalities.

nx.map

Based on vim.keymap.set()

nx.map({
   { ";w", "<Cmd>w<CR>" },
   { ";q", "<Cmd>confirm quit<CR>", desc = "Close Current Window" },
   { "<leader>ts", "<Cmd>set spell!<CR>", desc = "Toggle Spellcheck", wk_label = "Spellcheck" },
   { "<leader>tp", "<Cmd>MarkdownPreviewToggle<CR>", ft = "markdown", desc = "Toggle Markdown Preview" },
})
-- ...
nx.map({
   -- Line Navigation
   { { "j", "<Up>" }, "&wrap ? 'gj' : 'j'", "" },
   { { "k", "<Down>" }, "&wrap ? 'gk' : 'k'", "" },
   { "$", "&wrap ? 'g$' : '$'", "" },
   { "^", "&wrap ? 'g^' : '^'", mode = "" },
   -- 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 })
})

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] or mode key
  • opts: 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 description
      • ft: to create filetype specific mappings
    • {<wrapper_opts>}: to add options to all keymaps within a nx.map()
  • Feature overview
    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" })
    
  • Mode

    Specify a mode|mode[] other than "n" as index [3] or mode key (inline or in wrapper_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)
    
  • Wrapper options

    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 use nx.map for nvim-lspconfig#suggested-configuration

    ---@ common
    
    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
    
  • Custom which-key labels

    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 mappings desc key.

    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" } })
    
  • Type Annotations

    Allow your language server to assist you with hovers, completions, and diagnostics.

    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.

nx.hl

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 and bg|fg|link|…: value
  • ns_id: defaults to 0 and instead of being passed as index [1] it is optional as [3] or ns_id key
  • values: 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 a nx.hl()
  • Feature overview
    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
    
  • Wrapper options

    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     -- ╯
    

nx.au

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 the autocmd|autocmd[] to that group
    • {<wrapper_opts>} to add values to all autocmds within a nx.au()
  • Feature Description
    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
    
  • Autocommand groups

    Besides the usual adding to existing autocommand groups using the group key, it is possible to create autocommand groups on the fly with the create_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" })
    

nx.cmd

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 a nx.cmd()
  • Feature Description
    ---@ ╭── 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
    

nx.set

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).

  • 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)
    

Getting Started

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.

Examples

  • 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")
    

Reusability

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.

Contribution

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.


Credits