Esc the Matrix Neovim

Warning
See old post here before you proceed with neovim.
imagen

This will be configured with NixOS, I recommend kickstart.nvim if you are running other system. Or check my neovim setup with my ubuntu server - repo.

Table of Contents

Why Neovim?

Let’s be honest, because you find it cool - not using the mouse or just like me who like using the nipple (trackpoint). In this post let’s try to replicate IDE like vscode - this also a note for my stupid self who always forget the binding and commands.

Core Setup

Basic Config

Let’s use the template from NixOS official documenation. Configs will be using lua syntax.

 1programs.neovim = {
 2  enable = true;
 3  configure = {
 4    customRC = ''
 5    lua << EOF
 6
 7    vim.o.sessionoptions = "buffers,curdir,tabpages,winsize,help,globals,localoptions"
 8
 9    EOF
10    '';
11    packages.myVimPackage = with pkgs.vimPlugins; {
12      start = [ ctrlp ];
13    };
14  };
15};

Leader Keys

Use space as the leader key for all the custom shortcuts.

1-- [[ Leader keys ]]
2vim.g.mapleader = ' '
3vim.g.maplocalleader = ' '

Editor Options

Most of the function are also available in vim. These are some of the neccessary basic config.

 1vim.o.number = true
 2vim.o.mouse = 'a'
 3vim.o.showmode = false
 4vim.schedule(function() vim.o.clipboard = 'unnamedplus' end)
 5vim.o.breakindent = true
 6vim.o.undofile = true
 7vim.o.ignorecase = true
 8vim.o.smartcase = true
 9vim.o.signcolumn = 'yes'
10vim.o.updatetime = 250
11vim.o.timeout = true
12vim.o.timeoutlen = 500
13vim.o.splitright = true
14vim.o.splitbelow = true
15vim.o.list = true
16vim.opt.listchars = { tab = '» ', trail = '·', nbsp = '␣' }
17vim.o.inccommand = 'split'
18vim.o.cursorline = true
19vim.o.scrolloff = 10
20vim.o.confirm = true
  • Line numbers
  • Mouse support
  • No mode display (handled by statusline)
  • Clipboard
  • Persistent undo
  • Smart case search
  • Always show sign column
  • Invisible Character
  • Split right & below
  • Cursorline
  • Scroll offset = 10
  • Confirm before closing modified buffers

Basic Keymaps

Clear search highlight

1vim.keymap.set('n', '<Esc>', '<cmd>nohlsearch<CR>')

Window Navigation

Default keys uses Ctrl + w h j k l. But here we set it up to Ctrl + h j k l.

1-- [[ Basic keymaps ]]
2vim.keymap.set('n', '<C-h>', '<C-w><C-h>')
3vim.keymap.set('n', '<C-l>', '<C-w><C-l>')
4vim.keymap.set('n', '<C-j>', '<C-w><C-j>')
5vim.keymap.set('n', '<C-k>', '<C-w><C-k>')

Split

Default key and not overide.

Key Action
:split or :sp horizontal split
:vsplit or : vp vertical split
:q or :close close the windows and split
Ctrl + w s horizontal split
Ctrl + w v vertical split

For some reason the window resize is not working. Add this in customRC. Hold Ctrl + w h j k l to resize the current window (in split).

1vim.keymap.set("n", "<C-Up>",    "<cmd>resize +2<CR>",          { desc = "Increase window height" })
2vim.keymap.set("n", "<C-Down>",  "<cmd>resize -2<CR>",          { desc = "Decrease window height" })
3vim.keymap.set("n", "<C-Left>",  "<cmd>vertical resize -2<CR>", { desc = "Decrease window width" })
4vim.keymap.set("n", "<C-Right>", "<cmd>vertical resize +2<CR>", { desc = "Increase window width" })

Buffer

Buffer is a file loaded into memory for editing. Let’s just add a bufferline plugin for easy navigation like in vscode. Opened file are displayed in the top.

 1-- [[ Bufferline ]] 
 2require("bufferline").setup({
 3  options = {
 4    mode = "buffers",
 5    diagnostics = "nvim_lsp",
 6    separator_style = "slant",
 7    always_show_bufferline = false,
 8  },
 9})
10
11vim.keymap.set("n", "<S-h>", "<cmd>BufferLineCyclePrev<CR>", { desc = "Prev buffer" })
12vim.keymap.set("n", "<S-l>", "<cmd>BufferLineCycleNext<CR>", { desc = "Next buffer" })
13vim.keymap.set("n", "<leader>bd", "<cmd>bdelete<CR>", { desc = "Delete buffer" })

Add in pkgs.vimPlugins.

1bufferline-nvim
Key Action
:bnext or :bn switch next
:bprevious or : bp switch previous
:ls list buffer
:bd close buffer

Comment

Built-in commenting and uncommenting lines. If you are interested with more advance comment plugin, check this linl.

Key Action
gcc toggle command or uncomment
[num] gcc multiple line

Status

Status line

Shows: mode, branch, filename, diagnostics, LSP status. Plugin used is lualine.

 1-- [[ Lualine ]]
 2require("lualine").setup({
 3  options = {
 4    theme = "catppuccin",
 5    globalstatus = true,
 6    section_separators = "",
 7    component_separators = "",
 8  },
 9  sections = {
10    lualine_a = { "mode" },
11    lualine_b = { "branch" },
12    lualine_c = { { "filename", path = 1 } },
13    lualine_x = { "diagnostics", "lsp_status" },
14    lualine_y = { "filetype" },
15    lualine_z = { "location" },
16  },
17})

Add in pkgs.vimPlugins.

1lualine-nvim

Git

Lazygit
1vim.keymap.set("n", "<leader>gg", ":LazyGit<CR>", { silent = true, desc = "Open LazyGit" })

Add in pkgs.vimPlugins.

1lualine-nvim

Add in environment.systemPackages.

1lazygit
Git Signs

It displays visual indicators (signs) in the sign column to show the status of lines (added, modified, or deleted) compared to the latest Git revision.

 1 -- [[ GitSigns ]]
 2local ok, gitsigns = pcall(require, "gitsigns")
 3if ok then
 4  gitsigns.setup {
 5    signs = {
 6      add          = {text = '+'},
 7      change       = {text = '~'},
 8      delete       = {text = '_'},
 9      topdelete    = {text = '‾'},
10      changedelete = {text = '~'},
11    },
12    numhl = false,
13    linehl = false,
14    current_line_blame = true,
15    watch_gitdir = { interval = 1000, follow_files = true },
16    sign_priority = 6,
17    update_debounce = 100,
18  }
19end

Add in pkgs.vimPlugins.

1gitsigns-nvim

Theme

I’m using catppuccin(https://github.com/catppuccin/nvim). Some plugin have config for theme, look out for that.

 1
 2-- [[ Theme ]]
 3-- [[ Catppuccin Theme ]]
 4require("catppuccin").setup({
 5  flavour = "mocha", -- latte, frappe, macchiato, mocha
 6  background = {       -- Optional
 7    light = "latte",
 8    dark = "mocha",
 9  },
10  transparent_background = true,
11  term_colors = true,
12  dim_inactive = { enabled = true },
13  styles = {
14    comments = { "italic" },
15    functions = { "bold" },
16  },
17})
18
19vim.cmd.colorscheme("catppuccin") -- activates the theme

Add in pkgs.vimPlugins.

1catppuccin-nvim

Fuzzy Finder

Telescope for me more refined than fzf-lua.

  • Find files, live grep, buffers, help
  • Resume last search
  • Yank file path to clipboard (y / )
 1
 2-- [[ Telescope ]]
 3local telescope = require("telescope")
 4local actions = require("telescope.actions")
 5local action_state = require("telescope.actions.state")
 6local builtin = require('telescope.builtin')
 7
 8-- Setup Telescope
 9telescope.setup({
10  defaults = {
11    mappings = {
12      i = {
13        -- Press <C-y> in insert mode to yank path
14        ["<C-y>"] = function(prompt_bufnr)
15          local entry = action_state.get_selected_entry()
16          local path = entry.path or entry.filename
17          vim.fn.setreg("+", path) -- Yank to system clipboard (+)
18          vim.fn.setreg("", path)  -- Yank to unnamed register
19          print("Yanked: " .. path)
20          actions.close(prompt_bufnr)
21        end,
22      },
23      n = {
24        -- Press y in normal mode to yank path
25        ["y"] = function(prompt_bufnr)
26          local entry = action_state.get_selected_entry()
27          local path = entry.path or entry.filename
28          vim.fn.setreg("+", path)
29          vim.fn.setreg("", path)
30          print("Yanked: " .. path)
31          actions.close(prompt_bufnr)
32        end,
33      },
34    },
35  },
36  extensions = {
37    file_browser = {
38      hijack_netrw = true,    -- optionally hijack netrw
39      theme = "ivy",
40      mappings = {
41        i = {
42          ["<C-y>"] = function(prompt_bufnr)
43            local entry = action_state.get_selected_entry()
44            if not entry then return end
45            local path = entry.path or entry.filename
46            vim.fn.setreg("+", path)
47            vim.fn.setreg("", path)
48            print("Yanked: " .. path)
49            actions.close(prompt_bufnr)
50          end,
51        },
52        n = {
53          ["y"] = function(prompt_bufnr)
54            local entry = action_state.get_selected_entry()
55            if not entry then return end
56            local path = entry.path or entry.filename
57            vim.fn.setreg("+", path)
58            vim.fn.setreg("", path)
59            print("Yanked: " .. path)
60            actions.close(prompt_bufnr)
61          end,
62        },
63      },
64    },
65  },
66})
67
68-- Load file_browser extension
69telescope.load_extension('file_browser')
70
71-- Keymaps
72vim.keymap.set('n', '<Space>sf', builtin.find_files)
73vim.keymap.set('n', '<Space>sg', builtin.live_grep)
74vim.keymap.set('n', '<Space>sb', builtin.buffers)
75vim.keymap.set('n', '<Space>sh', builtin.help_tags)
76vim.keymap.set('n', '<leader>sr', builtin.resume)
77vim.keymap.set('n', '<leader>s.', builtin.oldfiles)
78
79-- File browser keymap
80vim.keymap.set('n', '<Space>fb', function()
81  telescope.extensions.file_browser.file_browser({
82    path = "%:p:h",    -- start in current file's directory
83    respect_gitignore = false,
84    hidden = true,
85  })
86end)

Add in pkgs.vimPlugins.

1telescope-nvim
2telescope-file-browser-nvim

Add in environment.systemPackages.

1xclip
Key Action
<Space> sf find file
<Space> sb live grep
<Space> sf buffers
<Space> sh help tags
<Space> sx resume
<Space> s. oldfiles
<Space> fg open file browser
Ctrl + y copy file path (while in sf)

File Browser

I’ve already added a file browser extension in telescope config above. But right now I’m testing oil, so I’ll might as well just put it here.

 1
 2-- [[ Oil.nvim ]]
 3local oil = require('oil')
 4
 5oil.setup({ default_file_explorer = false })
 6vim.keymap.set('n', '<Space>o', function() oil.open() end)
 7
 8-- [[ Format on save ]]
 9vim.api.nvim_create_autocmd('BufWritePre', {
10  callback = function()
11    vim.lsp.buf.format({ async = false })
12  end,
13})

Add in pkgs.vimPlugins.

1oil-nvim
Key Action
<Space> o open oil

Dashboard & Sessions

Greeter

Alpha-nvim to handle dashboard and greeter.

  • New file
  • Find file
  • Live grep
  • File explorer
  • Restore session
 1-- [[ Alpha Greeter ]]
 2        local alpha = require("alpha")
 3        local dashboard = require("alpha.themes.dashboard")
 4
 5        dashboard.section.header.val = {
 6          " ",
 7          "  ███╗   ██╗██╗██╗  ██╗",
 8          "  ████╗  ██║██║╚██╗██╔╝",
 9          "  ██╔██╗ ██║██║ ╚███╔╝ ",
10          "  ██║╚██╗██║██║ ██╔██╗ ",
11          "  ██║ ╚████║██║██╔╝ ██╗",
12          "  ╚═╝  ╚═══╝╚═╝╚═╝  ╚═╝",
13          " ",
14        }
15
16        dashboard.section.buttons.val = {
17          dashboard.button("e", "  New file", "<cmd>ene<CR>"),
18          dashboard.button("SPC sf", "  Find file"),
19          dashboard.button("SPC sg", "  Live grep"),
20          dashboard.button("SPC o", "  File explorer"),
21          dashboard.button("SPC sl", "  Restore session", "<cmd>SessionManager load_last_session<CR>"),
22          dashboard.button("q", "  Quit", "<cmd>qa<CR>"),
23        }
24
25        dashboard.section.footer.val = "Minimal Neovim on Nix ❄️"
26
27        alpha.setup(dashboard.opts)
28
29        -- [[ Session Manager ]]
30        require("auto-session").setup({
31            log_level = "info",
32            auto_session_enable_last_session = true,  -- load last session automatically
33            auto_session_root_dir = vim.fn.stdpath("data").."/sessions",
34            auto_session_enabled = true,
35            auto_save_enabled = true,                 -- autosave current session
36        })
37
38        vim.keymap.set("n", "<leader>qs", "<cmd>AutoSession save<CR>", { desc = "Session Save" })
39        vim.keymap.set("n", "<leader>ql", "<cmd>AutoSession restore<CR>", { desc = "Session Load" })
40        vim.keymap.set("n", "<leader>qd", "<cmd>AutoSession delete<CR>", { desc = "Session Stop" })

Add in pkgs.vimPlugins.

1alpha-nvim
2plenary-nvim

Session

Auto-save session and auto-restore session.

1-- [[ Session Manager ]]
2require("auto-session").setup({
3    log_level = "info",
4    auto_session_enable_last_session = true,  -- load last session automatically
5    auto_session_root_dir = vim.fn.stdpath("data").."/sessions",
6    auto_session_enabled = true,
7    auto_save_enabled = true,                 -- autosave current session
8})

Add in pkgs.vimPlugins.

1auto-session
Key Action
<Space> qs save
<Space> ql load
<Space> qd delete

Autocomplete & Snippet

Snippet

LuaSnip

  • Loads VSCode-style snippets
  • Jump with Tab / Shift-Tab
1-- [[ Autocomplete ]]
2local cmp_autopairs = require("nvim-autopairs.completion.cmp")
3local cmp = require('cmp')
4
5cmp.event:on("confirm_done", cmp_autopairs.on_confirm_done())
6
7local luasnip = require('luasnip')

Add in pkgs.vimPlugins.

1nvim-cmp
2cmp-nvim-lsp
3luasnip
4cmp_luasnip
5friendly-snippets
6nvim-autopairs

Autocomplete

 1-- =========================
 2--        AUTOCOMPLETE
 3-- =========================
 4
 5require('luasnip.loaders.from_vscode').lazy_load()
 6
 7cmp.setup({
 8  snippet = {
 9    expand = function(args)
10      luasnip.lsp_expand(args.body)
11    end,
12  },
13  mapping = cmp.mapping.preset.insert({
14    ['<C-Space>'] = cmp.mapping.complete(),
15    ['<CR>'] = cmp.mapping.confirm({ select = true }),
16    ['<Tab>'] = cmp.mapping(function(fallback)
17      if cmp.visible() then
18        cmp.select_next_item()
19      elseif luasnip.expand_or_jumpable() then
20        luasnip.expand_or_jump()
21      else
22        fallback()
23      end
24    end, { 'i', 's' }),https://github.com/hrsh7th/nvim-cmp
25    ['<S-Tab>'] = cmp.mapping(function(fallback)
26      if cmp.visible() then
27        cmp.select_prev_item()
28      elseif luasnip.jumpable(-1) then
29        luasnip.jump(-1)
30      else
31        fallback()
32      end
33    end, { 'i', 's' }),
34  }),
35  sources = {
36    { name = 'nvim_lsp' },
37    { name = 'luasnip' },
38  },
39})

Add in pkgs.vimPlugins.

1nvim-cmp
2cmp-nvim-lsp
3luasnip
4cmp_luasnip
5friendly-snippets
6nvim-autopairs

Autopairs

nvim-autopairs

  • Tree-sitter aware pairing
  • Integrated with completion confirm
1
2-- [[ Autopairs ]]
3require("nvim-autopairs").setup({
4  check_ts = true, -- use treesitter for smarter pairing (recommended)
5})

Add in pkgs.vimPlugins.

1nvim-autopairs

Language Server Protocol

LSP Installer

Mason to handle or manage language servers.

 1
 2-- [[ Mason (UI only) ]]
 3local mason = require('mason')
 4local mason_lspconfig = require('mason-lspconfig')
 5
 6
 7mason.setup()
 8mason_lspconfig.setup({ automatic_installation = false })
 9
10local on_attach = function(_, bufnr)
11  local opts = { buffer = bufnr }
12  vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts)
13  vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, opts)
14  vim.keymap.set('n', 'gr', vim.lsp.buf.references, opts)
15  vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, opts)
16  vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts)
17  vim.keymap.set('n', '<leader>rn', vim.lsp.buf.rename, opts)
18  vim.keymap.set('n', '<leader>ca', vim.lsp.buf.code_action, opts)
19  vim.keymap.set('n', '<leader>f', function()
20    vim.lsp.buf.format({ async = true })
21  end, opts)
22end
23
24local capabilities = require('cmp_nvim_lsp').default_capabilities()
25
26local servers = {
27  "lua_ls",
28  "ts_ls",
29  "gopls",
30  "pyright",
31  "bashls",
32  "nil_ls",
33}
34
35for _, server in ipairs(servers) do
36  vim.lsp.config(server, {
37    on_attach = on_attach,
38    capabilities = capabilities,
39  })
40  vim.lsp.enable(server)
41end

Add in pkgs.vimPlugins.

1nvim-lspconfig
2mason-nvim
3mason-lspconfig-nvim

Add in environment.systemPackages.

1lua-language-server
2nil
3nodePackages.typescript-language-server
4gopls
5pyright
6bash-language-server

Language

These are the language loaded. Di ko naman alam pano gamitin yang mga language nayan hahahaha.

1lua_ls: Lua
2ts_ls: TypeScript/JavaScript
3gopls: Go (Golang)
4pyright: Python
5bashls: Bash/Shell Script
6nil_ls: Nix 

Diagnostics

To make the error message beside the code minimal let’s add tiny-inline-diagnostic-nvim plugin.

 1-- Disable default virtual text
 2vim.diagnostic.config({
 3    virtual_text = false,
 4})
 5
 6-- tiny-inline-diagnostic setup
 7require("tiny-inline-diagnostic").setup({
 8  options = {
 9    show_source = "if_many",
10    multilines = true,
11    use_icons_from_diagnostic = true,
12    break_line = {
13      enabled = true,
14      after = 40,
15    },
16  },
17})
18
19-- Your existing keymaps (unchanged)
20vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, { desc = 'Go to previous diagnostic' })
21vim.keymap.set('n', ']d', vim.diagnostic.goto_next, { desc = 'Go to next diagnostic' })
22vim.keymap.set('n', '<leader>e', vim.diagnostic.open_float, { desc = 'Show diagnostic in float' })
23vim.keymap.set('n', '<leader>q', vim.diagnostic.setloclist, { desc = 'Set diagnostics in loclist' })

Add in pkgs.vimPlugins.

1tiny-inline-diagnostic-nvim

Linting

nvim-lint

 1
 2-- [[ Lint ]]
 3local lint = require('lint')
 4
 5lint.linters_by_ft = {
 6  javascript = { 'eslint' },
 7  typescript = { 'eslint' },
 8  javascriptreact = { 'eslint' },
 9  typescriptreact = { 'eslint' },
10  python = { 'pylint' },
11  go = { 'golangcilint' },
12  nix = { 'statix', 'deadnix' },
13  sh = { 'shellcheck' },
14}
15
16local lint_augroup = vim.api.nvim_create_augroup('lint', { clear = true })
17
18vim.api.nvim_create_autocmd({ 'BufEnter', 'BufWritePost', 'InsertLeave' }, {
19  group = lint_augroup,
20  callback = function()
21    lint.try_lint()
22  end,
23})
24
25vim.keymap.set('n', '<leader>l', function()
26  lint.try_lint()
27end, { desc = 'Run lint' })

Add in pkgs.vimPlugins.

1statix # Nix
2deadnix # Nix unused code
3eslint # JS/TS
4golangci-lint # Go
5pylint # Python
6shellcheck # Bash

Add in environment.systemPackages.

1nvim-lint

Complete Config

Config is too long, check this link.

Commands we learned along the way

Multiple line tab

Enter visual mode, and highlight the lines needed to add tab.

  1. ctrl + v + arrow key to select the block
  2. [number of tab] shift + .
  3. to decrease the tab: [number of tab] shift + ,

Copy all line

gg + V + G