Esc the Matrix Neovim
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
File Navigation & Search
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
- 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
- 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
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.
ctrl + v+ arrow key to select the block- [number of tab]
shift + . - to decrease the tab: [number of tab]
shift + ,
Copy all line
gg + V + G