Explore the transition to Lua support in Neovim configurations. Follow along as settings are tweaked and personalized, discovering plugin choices, themes, and the challenges faced along the way.

Disclaimer: I am not proficient in Lua. If there are any mistakes, please inform me.

Prologue

I’ve been using Vim for many years since my college days, during which time I’ve also developed several plugins1 2 for my personal use. Then, driven by curiosity, I switched to using Neovim about two years ago3. Most of the configurations and plugins were migrated from Vim. Fortunately, the legacy tools are still working.

Lua for Neovim

Let’s talk about now. Many new, advanced tools are being made for Neovim. Using Lua is a big change for Neovim, and lots of plugins are being redone in Lua. Some only show how to use Lua. I only knew a bit about Lua—it’s a type of code often used for making games, known for being light and easy to manage compared to Vimscript. (I get that spending time learning a language, like Vimscript, just for a text editor might seem a bit silly. But since I don’t use Lua every day, it might be the second language I learn just for an editor…)

I like using fancy plugins. However, to use those Lua plugins, I have to change the init system. I’ve made three or four attempts to convert my legacy settings to the Lua version, but it was quite painful. I believe a breakdown is necessary. I understand the benefit of Lua, especially its module system, which allows easy importing of various modules from files. However, after many years of using Vim, the experience made me gradually attempt to consolidate all settings into one file4.

Thus, let’s start from scratch.

$ mv ~/.config/nvim ~/.config/nvim_goodbye_vimscript

My environments

  • Ubuntu 20.04
  • WSL2 with Ubuntu 20.04
  • Rust: website

Recommended font

  • ryanoasis/nerd-fonts; My choice: CascadiaCode.zip
    • Install Caskaydia Cove Nerd Font Complete Mono Regular, Italic, Bold

Preinstall packages

$ sudo apt install lldb # lldb tool for rustaceanvim
$ sudo apt install build-essential cmake # for compiling rust package
$ curl -L https://deb.nodesource.com/nsolid_setup_deb.sh | sudo bash -s -- 20 && sudo apt install -y nodejs # nodejs for mason-lspconfig.nvim
$ cargo install stylua # to format lua code
$ cargo install tree-sitter-cli # for nvim-treesitter/nvim-treesitter
$ cargo install ripgrep # for telescope 's live_grep
$ rustup component add rust-src rust-analyzer # for mrcjkb/rustaceanvim

Install Neovim

I have tried using apt, snap, and binary downloading, all of which have been messy. Recently, I found a tool that seems to be a good choice for installing Neovim, and that tool is bob (Install Rust environment first). Uninstalling your old Neovim is recommanded before you run following commands.

$ cargo install bob-nvim
$ bob install 0.9.4
$ bob use 0.9.4
$ bob ls
┌───────────┬─────────────┐
│  Version  │  Status     │
├───────────┼─────────────┤
│  v0.9.4   │  Used       │
└───────────┴─────────────┘
$ echo "export PATH=\$PATH:\$HOME/.local/share/bob/nvim-bin" >> ~/.bashrc

Setup for Virtual Environment

Install pyenv and target python version

$ sudo apt install build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev curl libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev # https://github.com/pyenv/pyenv/wiki#suggested-build-environment
$ sudo apt install unzip # lsp clangd
$ curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash
$ echo 'export PATH="/home/'`whoami`'/.pyenv/bin:$PATH"' >> ~/.bashrc
$ echo 'eval "$(pyenv init -)"' >> ~/.bashrc
$ echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.bashrc
$ source ~/.bashrc
$ pyenv install --list | grep " 3\.[8]"
$ pyenv install -v 3.8.10

Setup for neovim

$ pyenv virtualenv 3.8.10 neovim3
$ pyenv activate neovim3
$ ~/.pyenv/versions/neovim3/bin/python -m pip install pynvim

Config via Lua

$ mkdir ~/.config/nvim -p
$ vim ~/.config/nvim/init.lua

Hello Lua

Hello Lua

OuO

Mapping from VimL to Lua

key-value assignment

The global set is changed to vim.opt.

set tabstop=4
set softtabstop=4
vim.opt.tabstop = 4
vim.opt.softtabstop = 4

on/off setting

Same as key-value set.

" nobackup + writebackup = backup current file, deleted afterwards (default)
set nobackup
set writebackup
-- nobackup + writebackup = backup current file, deleted afterwards (default)
vim.opt.backup = false
vim.opt.writebackup = true

value append

" let labels not auto indent
set cinoptions+=L0
-- let labels not auto indent
vim.opt.cinoptions:append({ "L0" })

global, plugin functions

line("$")
codeium#Accept()
vim.fn.line("$")
vim.fn["codeium#Accept"]()

exec normal

:exec "normal! \zz"
vim.cmd.normal('\zz"')

autocmd

" let cursor in the middle of screen when entering vim
autocmd VimEnter * :exec "normal! \zz"

" return to last edit position when opening files
autocmd BufReadPost *
            \ if line("'\"") > 0 && line("'\"") <= line('$') |
            \   exe "normal! g`\"" |
            \ endif
local autocmd = vim.api.nvim_create_autocmd -- Create autocommand

-- let cursor in the middle of screen when entering vim
autocmd("VimEnter", { command = vim.cmd.normal('\zz"') })

-- return to last edit position when opening files
autocmd("BufReadPost", {
	callback = function()
		if vim.fn.line("'\"") > 0 and vim.fn.line("'\"") <= vim.fn.line("$") then
			vim.cmd.normal('g`"')
		end
	end,
})

keymap

" cancel highlight after searching
noremap <M-n> :noh<CR>

" background vim
inoremap <C-z> <C-o><C-z>
-- " cancel highlight after searching
vim.keymap.set("n", "<M-n>", ":noh<CR>",
	{ noremap = true, desc = "Cancel highlight" })

-- background vim in insert mode
vim.keymap.set("i", "<C-z>", "<C-o><C-z>",
	{ noremap = true, desc = "Background vim in insert mode" })

Plugins

The plugin manager comes first. I’ve opted for lazy.nvim, which doesn’t need an ‘install’ command. Great choice. For the editor’s appearance, I’ve been using gruvbox for about a year. It has a nice color setting. My plugins are functioning well, and I might add the comment format for Lua5. Telescope serves as a powerful search tool with a fuzzy finder, making it a smart replacement for my limited brain power. LSP configuration is quite challenging. I’m struggling to find a simple, unified example for setup. I’m starting to miss coc.vim…

Plugin Manager: Lazy.nvim

Automatically install all listed plugins. Use :Lazy check to manually verify the updates for the installed plugins.

local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
	vim.fn.system({
		"git",
		"clone",
		"--filter=blob:none",
		"https://github.com/folke/lazy.nvim.git",
		"--branch=stable", -- latest stable release
		lazypath,
	})
end
vim.opt.rtp:prepend(lazypath)

Selected plugins

  • ellisonleao/gruvbox.nvim: colorscheme
  • folke/neodev.nvim: neovim
  • rhysd/committia.vim: git commit message editor
  • lewis6991/gitsigns.nvim: git inline status
  • RRethy/vim-illuminate: automatically highlighting other uses of the word under the cursor
  • karb94/neoscroll.nvim: smooth scrolling
  • alpertuna/vim-header: header of the file
  • lukas-reineke/indent-blankline.nvim: improved Yggdroot/indentLine
  • tpope/vim-sleuth: automatically adjusts ‘shiftwidth’ and ’expandtab’
  • aben20807/vim-runner: My muscle for runner
  • aben20807/vim-commenter: My muscle for code comment
  • nvim-telescope/telescope.nvim: improved junegunn/fzf
  • justinmk/vim-sneak: the missing motion for Vim
  • mbbill/undotree: undo history
  • folke/which-key.nvim: help
  • nvim-treesitter/nvim-treesitter: syntax
  • Exafunction/codeium.vim: AI coding
  • neovim/nvim-lspconfig: LSP
  • hrsh7th/nvim-cmp: completion
  • nvim-lualine/lualine.nvim: status line

One for All: init.lua

Show the full configuration (init.lua)
--[[--
File              : init.lua
Author            : Huang, Po-Hsuan <aben20807@gmail.com>
Last Modified Date: 2023-11-09 09:58:25
--]]

-- "alpertuna/vim-header" --
vim.g.header_field_author = "Huang, Po-Hsuan"
vim.g.header_field_author_email = "aben20807@gmail.com"

vim.g.mapleader = " "

-- Setup for Virtual Environment
vim.g.python3_host_prog = "~/.pyenv/versions/neovim3/bin/python"
-- "aben20807/vim-commenter" --
vim.g.commenter_use_default_mapping = false

--------------------
-- Plugin Manager --
--------------------
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
	vim.fn.system({
		"git",
		"clone",
		"--filter=blob:none",
		"https://github.com/folke/lazy.nvim.git",
		"--branch=stable", -- latest stable release
		lazypath,
	})
end
vim.opt.rtp:prepend(lazypath)

--------------------
-- Plugins --
--------------------
require("lazy").setup({
	{
		"ellisonleao/gruvbox.nvim",
		priority = 1000,
		config = function()
			vim.o.background = "dark"
			vim.cmd([[colorscheme gruvbox]])
		end,
		opts = ...,
	},
	{ "folke/neodev.nvim", opts = {} },
	{
		-- git commit message editor
		"rhysd/committia.vim",
		event = 'BufEnter',
	},
	{
		"lewis6991/gitsigns.nvim",
		event = 'BufEnter',
		config = function()
			require('gitsigns').setup()
		end
	},
	{
		-- automatically highlighting other uses of the word under the cursor
		"RRethy/vim-illuminate",
		event = 'BufEnter',
	},
	{
		-- smooth scrolling
		"karb94/neoscroll.nvim",
		event = 'BufEnter',
		config = function()
			require("neoscroll").setup()
		end,
	},
	{
		"alpertuna/vim-header",
		event = 'BufEnter',
		config = function()
			vim.g.header_field_timestamp_format = "%Y-%m-%d %H:%M:%S"
			vim.g.header_field_timestamp = 0
			vim.g.header_field_modified_by = 0
			vim.keymap.set("n", "<F4>", ":AddHeader<CR>", { desc = "Add Header" })
		end,
	},
	{
		"lukas-reineke/indent-blankline.nvim",
		main = "ibl",
		opts = {},
		config = function()
			require("ibl").setup()
		end,
	},
	{
		-- automatically adjusts 'shiftwidth' and 'expandtab'
		"tpope/vim-sleuth",
	},
	{
		"aben20807/vim-runner",
		event = 'BufEnter',
		config = function()
			-- c
			vim.g.runner_c_executable = "gcc-8"
			vim.g.runner_c_compile_options = "-std=gnu99 -Wall -O2"
			-- c++
			vim.g.runner_cpp_executable = "g++-8"
			vim.g.runner_cpp_compile_options = "-std=c++17 -Wall -lm -O2 -pipe"
		end,
	},
	{
		"aben20807/vim-commenter",
		event = 'BufEnter',
		config = function()
			vim.keymap.set("n", "<C-j>", ":<C-u>call commenter#Comment()<CR>", { noremap = true, desc = "Comment" })
			vim.keymap.set("i", "<C-j>", "<ESC>l:<C-u>call commenter#Comment()<CR>i",
				{ noremap = true, desc = "Comment" })
			vim.keymap.set("v", "<C-j>", ":<C-u>call commenter#CommentV(visualmode())<CR>",
				{ noremap = true, desc = "Comment" })
		end,
	},
	{
		"nvim-telescope/telescope.nvim",
		tag = "0.1.4",
		dependencies = { "nvim-lua/plenary.nvim" },
		config = function()
			local builtin = require("telescope.builtin")
			vim.keymap.set("n", "<leader>ff", builtin.find_files, { desc = "Find files" })
			vim.keymap.set("n", "<leader>fg", builtin.live_grep, { desc = "Live grep" })
			vim.keymap.set("n", "<leader>fb", builtin.buffers, { desc = "Find buffers" })
			vim.keymap.set("n", "<leader>fh", builtin.help_tags, { desc = "Help tags" })
		end,
	},
	{
		"justinmk/vim-sneak",
		event = 'BufEnter',
		config = function()
			vim.cmd("let g:sneak#s_next = 1")
		end,
	},
	{
		"mbbill/undotree",
		config = function()
			vim.keymap.set("n", "<leader>un", vim.cmd.UndotreeToggle, { desc = "Toggle undotree" })
		end,
	},
	{
		"folke/which-key.nvim",
		event = "VeryLazy",
		init = function()
			vim.o.timeout = true
			vim.o.timeoutlen = 300
		end,
	},
	{
		"nvim-treesitter/nvim-treesitter",
		build = ":TSUpdate",
		config = function()
			local configs = require("nvim-treesitter.configs")
			configs.setup({
				ensure_installed = { "c", "cpp", "lua", "vim", "vimdoc", "rust", "gitcommit", "make", "python" },
				sync_install = false,
				highlight = { enable = true },
				indent = { enable = true },
			})
		end,
	},
	{
		"Exafunction/codeium.vim",
		branch = "main",
		event = 'BufEnter',
		config = function()
			vim.g.codeium_no_map_tab = true
			vim.keymap.set("i", "<C-g>", function()
				return vim.fn["codeium#Accept"]()
			end, { expr = true, desc = "Accept [AI]" })
			vim.keymap.set("i", "<c-t>", function()
				return vim.fn["codeium#CycleCompletions"](1)
			end, { expr = true, desc = "Next [AI]" })
			vim.keymap.set("i", "<c-b>", function()
				return vim.fn["codeium#CycleCompletions"](-1)
			end, { expr = true, desc = "Prev [AI]" })
			vim.keymap.set("i", "<c-x>", function()
				return vim.fn["codeium#Clear"]()
			end, { expr = true, desc = "Clear [AI]" })
		end,
	},
	{
		"neovim/nvim-lspconfig",
		dependencies = {
			{
				"williamboman/mason-lspconfig.nvim",
				dependencies = { "williamboman/mason.nvim" },
			},
			{
				"mrcjkb/rustaceanvim",
				version = "^3", -- Recommended
				ft = { "rust" },
				dependencies = { "mfussenegger/nvim-dap" },
			},
		},
		opts = {
			inlay_hints = { enabled = true },
		},
		config = function()
			require("mason").setup({
				PATH = "prepend", -- "skip" seems to cause the spawning error
			})
			require("mason-lspconfig").setup({
				ensure_installed = {
					-- "rust_analyzer",
					"pyright",
					"marksman",
					"bashls",
					"clangd",
					"lua_ls",
				},
				-- handlers = {
				-- 	rust_analyzer = function() end, -- use rustaceanvim
				-- },
			})
			local lspconfig = require("lspconfig")
			-- lspconfig.rust_analyzer.setup({})
			lspconfig.pyright.setup({})
			lspconfig.marksman.setup({})
			lspconfig.bashls.setup({})
			lspconfig.clangd.setup({})
			lspconfig.lua_ls.setup({
				settings = {
					Lua = {
						runtime = {
							-- Tell the language server which version of Lua you're using
							-- (most likely LuaJIT in the case of Neovim)
							version = "LuaJIT",
						},
						diagnostics = {
							-- https://github.com/neovim/neovim/issues/21686
							-- Get the language server to recognize the `vim` global
							globals = {
								"vim",
								"buffer",
								"require",
							},
						},
						-- Do not send telemetry data containing a randomized but unique identifier
						telemetry = {
							enable = false,
						},
						hint = { enable = true },
					},
				},
			})
		end,
	},
	{
		"https://git.sr.ht/~whynothugo/lsp_lines.nvim",
		config = function()
			require("lsp_lines").setup()
			vim.diagnostic.config({
				virtual_text = false,
			})
			vim.keymap.set("n", "<Leader>l", require("lsp_lines").toggle, { desc = "Toggle lsp_lines" }
			)
		end,
	},
	{
		"hrsh7th/nvim-cmp",
		dependencies = {
			"hrsh7th/cmp-nvim-lsp",
			"hrsh7th/cmp-nvim-lua",
			"hrsh7th/cmp-buffer",
			"hrsh7th/cmp-path",
			"hrsh7th/cmp-cmdline",
			-- luasnip
			"saadparwaiz1/cmp_luasnip",
			"L3MON4D3/LuaSnip",
			-- "simrat39/rust-tools.nvim",
		},
		config = function()
			--Set completeopt to have a better completion experience
			-- :help completeopt
			-- menuone: popup even when there's only one match
			-- noinsert: Do not insert text until a selection is made
			-- noselect: Do not select, force to select one from the menu
			-- shortness: avoid showing extra messages when using completion
			-- updatetime: set updatetime for CursorHold
			vim.opt.completeopt = { "menuone", "noselect", "noinsert" }
			vim.opt.shortmess = vim.opt.shortmess + { c = true }
			vim.api.nvim_set_option("updatetime", 300)
			vim.diagnostic.config({
				virtual_text = false,
				signs = true,
				update_in_insert = true,
				underline = true,
				severity_sort = false,
				float = {
					border = "rounded",
					source = "always",
					header = "",
					prefix = "",
					format = function(diagnostic)
						return string.format(
							"%s (%s) [%s]",
							diagnostic.message,
							diagnostic.source,
							diagnostic.code or diagnostic.user_data.lsp.code
						)
					end,
				},
			})
			vim.keymap.set('n', '<space>e', vim.diagnostic.open_float, { desc = "Open diagnostic [CMP]" })
			vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, { desc = "Previous diagnostic [CMP]" })
			vim.keymap.set('n', ']d', vim.diagnostic.goto_next, { desc = "Next diagnostic [CMP]" })
			vim.keymap.set('n', '<space>q', vim.diagnostic.setloclist, { desc = "Open diagnostics list [CMP]" })

			local cmp = require("cmp")
			cmp.setup({
				-- Enable LSP snippets
				snippet = {
					expand = function(args)
						require('luasnip').lsp_expand(args.body)
					end,
				},
				mapping = {
					["<S-Tab>"] = cmp.mapping.select_prev_item(),
					["<Tab>"] = cmp.mapping.select_next_item(),
					["<C-S-f>"] = cmp.mapping.scroll_docs(-4),
					["<C-f>"] = cmp.mapping.scroll_docs(4),
					["<C-Space>"] = cmp.mapping.complete(),
					["<C-e>"] = cmp.mapping.close(),
					["<CR>"] = cmp.mapping.confirm({
						behavior = cmp.ConfirmBehavior.Insert,
						select = true,
					}),
				},
				-- Installed sources:
				sources = {
					{ name = "path" },                        -- file paths
					{ name = "nvim_lsp",               keyword_length = 3 }, -- from language server
					{ name = "nvim_lsp_signature_help" },     -- display function signatures with current parameter emphasized
					{ name = "nvim_lua",               keyword_length = 2 }, -- complete neovim's Lua runtime API such vim.lsp.*
					{ name = "buffer",                 keyword_length = 2 }, -- source current buffer
					{ name = "vsnip",                  keyword_length = 2 }, -- nvim-cmp source for vim-vsnip
					{ name = "calc" },                        -- source for math calculation
				},
				window = {
					completion = cmp.config.window.bordered(),
					documentation = cmp.config.window.bordered(),
				},
				formatting = {
					fields = { "menu", "abbr", "kind" },
					format = function(entry, item)
						local menu_icon = {
							nvim_lsp = "L",
							vsnip = "S",
							buffer = "B",
							path = "P",
						}
						item.menu = menu_icon[entry.source.name]
						return item
					end,
				},
			})

			-- local rt = require("rust-tools")
			-- rt.setup({
			-- 	server = {
			-- 		on_attach = function(_, bufnr)
			-- 			-- Hover actions
			-- 			vim.keymap.set("n", "<C-space>", rt.hover_actions.hover_actions,
			-- 				{ buffer = bufnr, desc = "Hover actions [RUST]" })
			-- 			-- Code action groups
			-- 			vim.keymap.set("n", "<Leader>aa", rt.code_action_group.code_action_group,
			-- 				{ buffer = bufnr, desc = "Code action groups [RUST]" })
			-- 		end,
			-- 	},
			-- })
		end,
	},
	{
		'nvim-lualine/lualine.nvim',
		requires = { 'nvim-tree/nvim-web-devicons', opt = true },
		config = function()
			require('lualine').setup {
				options = {
					icons_enabled = true,
					theme = 'gruvbox',
					component_separators = { left = '', right = '' },
					section_separators = { left = '', right = '' },
					disabled_filetypes = {
						statusline = {},
						winbar = {},
					},
					ignore_focus = {},
					always_divide_middle = true,
					globalstatus = false,
					refresh = {
						statusline = 1000,
						tabline = 1000,
						winbar = 1000,
					}
				},
				sections = {
					lualine_a = { { 'mode', fmt = function(str) return str:sub(1, 1) end } },
					lualine_b = { 'branch', {
						'diff',
						symbols = { added = '+', modified = '~', removed = '-' },
					}, {
						'diagnostics',
						symbols = { error = 'E', warn = 'W', info = 'I', hint = 'H' },
					} },
					lualine_c = { 'filename' },
					lualine_x = { 'encoding', 'fileformat', 'filetype' },
					lualine_y = { 'progress' },
					lualine_z = { 'location' }
				},
				inactive_sections = {
					lualine_a = {},
					lualine_b = {},
					lualine_c = { 'filename' },
					lualine_x = { 'location' },
					lualine_y = {},
					lualine_z = {}
				},
				tabline = {},
				winbar = {},
				inactive_winbar = {},
				extensions = {}
			}
		end
	},
})

------------
-- Keymap --
------------
vim.keymap.set("n", "<leader>pv", vim.cmd.Ex, { desc = "Open file explorer" })

-- background vim in insert mode
vim.keymap.set("i", "<C-z>", "<C-o><C-z>", { noremap = true, desc = "Background vim in insert mode" })

-- indent
vim.keymap.set("n", "<", "<<", { desc = "Indent left" })
vim.keymap.set("n", ">", ">>", { desc = "Indent right" })
vim.keymap.set("v", "<", "<gv", { desc = "Indent left" })
vim.keymap.set("v", ">", ">gv", { desc = "Indent right" })

-- move screen smoothly
vim.keymap.set("n", "<up>", ":lua require('neoscroll').scroll(-0.10, false, 100)<CR>",
	{ noremap = true, desc = "Scroll up" })
vim.keymap.set("n", "<down>", ":lua require('neoscroll').scroll(0.10, false, 100)<CR>",
	{ noremap = true, desc = "Scroll down" })
vim.keymap.set("n", "<C-up>", ":lua require('neoscroll').scroll(-vim.wo.scroll, true, 200)<CR>",
	{ noremap = true, desc = "Scroll up multiline" })
vim.keymap.set("n", "<C-down>", ":lua require('neoscroll').scroll(vim.wo.scroll, true, 200)<CR>",
	{ noremap = true, desc = "Scroll down multiline" })

-- left and right can switch buffers
vim.keymap.set("n", "<left>", ":bp<CR>", { noremap = true, desc = "Previous buffer" })
vim.keymap.set("n", "<right>", ":bn<CR>", { noremap = true, desc = "Next buffer" })

-- Move by line
vim.keymap.set("n", "j", "(v:count == 0 ? 'gj' : 'j')", { silent = true, expr = true, remap = false })
vim.keymap.set("n", "k", "(v:count == 0 ? 'gk' : 'k')", { silent = true, expr = true, remap = false })

-- Search results centered please
vim.keymap.set("n", "n", "nzz", { noremap = true, silent = true })
vim.keymap.set("n", "N", "Nzz", { noremap = true, silent = true })
vim.keymap.set("n", "*", "*zz", { noremap = true, silent = true })
vim.keymap.set("n", "#", "#zz", { noremap = true, silent = true })
vim.keymap.set("n", "g*", "g*zz", { noremap = true, silent = true })

-- " cancel highlight after searching
vim.keymap.set("n", "<M-n>", ":noh<CR>", { noremap = true, desc = "Cancel highlight" })

-- " search select text by pressing // in visual mode
-- " Ref: http://vim.wikia.com/wiki/Search_for_visually_selected_text
vim.keymap.set("v", "//", "y/<C-r>=escape(@\",'/')<CR><CR>", { noremap = true, desc = "Search select text" })

-- " map ctrl-a to select whole text
vim.keymap.set("n", "<C-a>", "ggVG", { noremap = true, desc = "Select whole text" })

-- Not overwrite paste buffer after pasting
vim.keymap.set("x", "p", "P", { noremap = true, desc = "Paste without overwriting buffer" })

if vim.lsp.inlay_hint then
	vim.keymap.set("n", "<leader>in", function()
		vim.lsp.inlay_hint(0, nil)
	end, { desc = "Toggle Inlay Hints" })
end

vim.keymap.set("n", "<leader>gD", vim.lsp.buf.declaration,
	{ noremap = true, silent = true, desc = "Go to declaration [LSP]" })
vim.keymap.set("n", "<leader>gd", vim.lsp.buf.definition,
	{ noremap = true, silent = true, desc = "Go to definition [LSP]" })
vim.keymap.set("n", "<leader>gi", vim.lsp.buf.implementation,
	{ noremap = true, silent = true, desc = "Go to implementation [LSP]" })
vim.keymap.set("n", "<leader>gr", vim.lsp.buf.references,
	{ noremap = true, silent = true, desc = "Go to references [LSP]" })
vim.keymap.set("n", "<leader>gy", vim.lsp.buf.type_definition,
	{ noremap = true, silent = true, desc = "Go to type definition [LSP]" })
vim.keymap.set("n", "<leader>rn", vim.lsp.buf.rename, { noremap = true, silent = true, desc = "Rename [LSP]" })
vim.keymap.set("n", "<leader>fs", vim.lsp.buf.format, { noremap = true, silent = true, desc = "Format [LSP]" })

----------------
-- Automation --
----------------
-- https://github.com/brainfucksec/neovim-lua/blob/dc7ba6f850bd576b3777ab56d081aa845ed5c946/nvim/lua/core/autocmds.lua
local augroup = vim.api.nvim_create_augroup -- Create/get autocommand group
local autocmd = vim.api.nvim_create_autocmd -- Create autocommand

-- Highlight on yank
augroup("YankHighlight", { clear = true })
autocmd("TextYankPost", {
	group = "YankHighlight",
	callback = function()
		vim.highlight.on_yank({ higroup = "IncSearch", timeout = "1000" })
	end,
})

-- Don't auto commenting new lines
autocmd("BufEnter", {
	pattern = "",
	command = "set fo-=c fo-=r fo-=o",
})

-- let cursor in the middle of screen when entering vim
autocmd("VimEnter", { command = vim.cmd.normal('\zz"') })

-- return to last edit position when opening files
autocmd("BufReadPost", {
	callback = function()
		if vim.fn.line("'\"") > 0 and vim.fn.line("'\"") <= vim.fn.line("$") then
			vim.cmd.normal('g`"')
		end
	end,
})

-- Autoformat on save
vim.api.nvim_create_autocmd("BufWritePre", {
	buffer = buffer,
	callback = function()
		vim.lsp.buf.format { async = false }
	end
})

-- Check if file changed on disk
autocmd("FileChangedShellPost", {
	callback = function()
		vim.api.nvim_echo({ { "File changed on disk. Buffer reloaded.", "WarningMsg" } }, true, {})
		vim.api.nvim_command('checktime')
	end
})

-- Set up a timer to run the 'fresh' function every 5000 milliseconds
local timer = vim.loop.new_timer()
timer:start(5000, 0, vim.schedule_wrap(function()
	-- Check if buffer "[Command Line]" exists and then perform a checktime
	if vim.fn.bufexists("[Command Line]") == false then
		vim.api.nvim_command('checktime')
	end
end))

------------
-- Editor --
------------
-- https://github.com/arturgoms/nvim/blob/a8c23e36010746c6bc8e6b6b567433894833ca1d/lua/user/options.lua
local options = {
	updatetime = 300,    -- faster completion (4000ms default)
	autoindent = true,   -- copy indent from current line when starting new
	smartindent = true,  -- make indenting smarter again
	timeoutlen = 1000,   -- time to wait for a mapped sequence to complete (in milliseconds)
	encoding = "utf-8",  -- the encoding written to a file
	fileencoding = "utf-8", -- the encoding written to a file
	scrolloff = 2,       -- minimal number of screen lines to keep above and below the cursor
	showmode = false,    -- we don't need to see things like -- INSERT -- anymore
	hidden = true,       -- allow buffer switching without saving
	wrap = false,        -- don't wrap lines
	joinspaces = false,  -- insert two spaces after a '.', '?' and '!'
	signcolumn = "yes",  -- always show the sign column, otherwise it would shift the text each time
	splitright = true,   -- force all vertical splits to go to the right of current window
	splitbelow = true,   -- force all horizontal splits to go below current window
	incsearch = true,    -- incremental search
	ignorecase = true,   -- ignore case
	smartcase = true,    -- smart case
	gdefault = true,     -- use g flag on search
	-- nobackup + writebackup = backup current file, deleted afterwards (default)
	backup = false,
	writebackup = true,
	swapfile = false,
	tabstop = 4,          -- insert 4 spaces for a tab
	softtabstop = 4,      -- insert 4 spaces for a tab
	expandtab = true,     -- convert tabs to spaces
	shiftwidth = 4,       -- the number of spaces inserted for each indentation
	virtualedit = "onemore", -- allow the cursor to move just past the end of the line
	mouse = "a",          -- allow the mouse to be used in neovim
	cmdheight = 1,        -- more space in the neovim command line for displaying messages
	cursorline = true,    -- highlight the current line
	termguicolors = true, -- set term gui colors (most terminals support this)
	number = true,        -- set numbered lines
	numberwidth = 4,      -- set number column width to 2 {default 4}
	conceallevel = 0,     -- so that `` is visible in markdown files
	history = 1000,       -- sets how many lines of history VIM has to remember
	modifiable = true,    -- allow changing the buffer
	autoread = true,      -- automatically read changed files
}

for k, v in pairs(options) do
	vim.opt[k] = v
end

vim.opt.wildignore:append({ "*.o", "*.obj", "*.pyc" })
-- let labels not auto indent
vim.opt.cinoptions:append({ "L0" })


------------
-- Legacy --
------------
vim.api.nvim_exec(
	[[
" let undo history not be clear after changing buffer
" Ref: https://stackoverflow.com/a/22676189/6734174
let vimDir = '$HOME/.config/nvim/'
let &runtimepath .= ',' . vimDir
" Keep undo history across sessions by storing it in a file
if has('persistent_undo')
    let myUndoDir = expand(vimDir . '/undodir')
    " Create dirs
    silent! call mkdir(myUndoDir, 'p')
    let &undodir = myUndoDir
    set undofile
    set undolevels=1000
    set undoreload=10000
endif

" TMUX key
" Ref: https://superuser.com/a/402084
if &term =~ '^screen'
    " tmux will send xterm-style keys when its xterm-keys option is on
    execute "set <xUp>=\e[1;*A"
    execute "set <xDown>=\e[1;*B"
    execute "set <xRight>=\e[1;*C"
    execute "set <xLeft>=\e[1;*D"
endif
]],
	false
)

----------------------
-- disable provider --
----------------------
vim.g.loaded_node_provider = 0
vim.g.loaded_perl_provider = 0
vim.g.loaded_ruby_provider = 0
  • :Codeium Auth: add token from the given url

Epilogue

Finally, all configurations are in place. However, these settings might undergo frequent modifications as I use this version. The journey to transition to Lua support has been lengthy but rewarding. Let’s continue to explore and refine.

Chat-GPT has fine-tuned some grammar and words in this post.

References

  • ⊛ Back to top
  • ⊛ Go to bottom