TOC
Porting Neovim config in Lua … Why and How ?
at some point of time , we all have to tweak our configs , and time by time , we all make our configs more complex .
So today , we are going to port our neovim Config in Lua language …
But , Why ?
well the most simple answer would be , to make more complex and functional config . When Bram Moolenaar made vim , he made vimscript to write configs for vim . But the main fact is , that it is not functional language so we cann’t write complex code to do certain tasks.
Lua in the rescue
Neovim supports Lua from default and coolest part is it is a functional programming language , an example would be , you can write games in Lua with Love2d game engine. So we can write multi-functional and complex code with ease. So let’s jump into it .
How ?
Now comes up the real part , tide your seatbelts and let’s dive into it …
The Base
Neovim supports init.lua file instead of init.vim , so we are gonna make a init.lua file inside ~/.config/nvim/
Remember to backup or delete your
init.vimif existed
Other Lua files
To manage your config with ease , you should break down your config with naming conventions for different , tasks , like for mappings , mappings.lua etc.
The Basic File structure of nvim config is like that:
📂 ~/.config/nvim
├── 📁 after
├── 📁 ftplugin
├── 📂 lua
│ ├── 🌑 myluamodule.lua
│ └── 📂 other_modules
│ ├── 🌑 anothermodule.lua
│ └── 🌑 init.lua
├── 📁 pack
├── 📁 plugin
├── 📁 syntax
└── init.lua
so lua files are typically located inside Lua/ and it can have submodules as well.
To load myluamodule.lua we have to call:
require('myluamodule')
similarly to load other_modules/anothermodule.lua we have to call:
require('other_modules.anothermodule')
Notice that while requiring these files, we are not calling .lua extension.
another thing to Remember if you are calling any lua file in your init.vim , you have to append lua before everyline:
lua require('myluamodule')
lua require('other_modules.anothermodule')
just like this.
Another thing to remenber if you want to call the whole other_modules/ at once , you can simply call:
require('other_modules')
but in that case you have to have a init.lua under that directory.
Tip
to call a bigger chunk of Lua code inside your init.vim , just call like this:
lua << EOF
local mod = require('myluamodule')
local chars = {'\', '|', '~'}
for s, m in ipairs(chars) do
mod.method(v)
end
print(chars)
EOF
To source a lua file , just call in command mode:
:luafile % # for the current file
:luafile some/thing/file.lua # for a certain file
luafile vs require():
You might be wondering what the difference between lua require() and luafile is and whether you should use one over the other. They have different use cases:
require():- is a built-in Lua function. It allows you to take advantage of Lua’s module system
- searches for modules in
luafolders in yourruntimepath - keeps track of what modules have been loaded and prevents a script from being parsed and executed a second time. If you change the file containing the code for a module and try to
require()it a second time while Neovim is running, the module will not actually update
:luafile:- is an Ex command. It does not support modules
- takes a path that is either absolute or relative to the working directory of the current window
- executes the contents of a script regardless of whether it has been executed before
The sets
The most common part of any vim configuratins is sets, like:
set smarttab
set number
set relativenumber
set cursorline
set termguicolors
set nobackup
set filetype plugin on
set indent on
set syntex on
To achieve the same thing in lua we have a different but more verbose approach:
vim.o.{option}: global optionsvim.bo.{option}: buffer-local optionsvim.wo.{option}: window-local optionsvim.b.{name}: buffer variablesvim.w.{name}: window variablesvim.t.{name}: tabpage variablesvim.v.{name}: predefined Vim variablesvim.env.{name}: environment variables- and at last
vim.opt.{name}: The Global wrapper for everything
So now the sets will be like:
-- Ignorecases
vim.o.ignorecase = true
vim.o.smartcase = true
-- Search
vim.o.hlsearch = true
vim.o.incsearch = true
vim.o.inccommand = "split"
-- Vim UI
vim.o.smartindent = true
vim.o.wrap = false
vim.o.colorcolumn = '120'
vim.o.showcmd = true
vim.o.tabstop = 4
vim.o.softtabstop = 4
vim.o.signcolumn="yes"
vim.o.scrolloff = 8
vim.o.showmode = false
vim.o.completeopt = "menuone,noinsert,noselect"
vim.o.hidden = true
-- vim.o.include
vim.o.display = "lastline"
vim.o.backspace = "indent,eol,start"
-- Visuals
vim.o.syntax = "enable"
-- vim.o.filetype = "plugin on"
vim.o.t_Co="256"
-- vim.api.nvim_set_option('t_Co',256)
vim.o.termguicolors = true
-- vim.o.encodingvim.o.hidden = true
vim.o.include = ""
vim.o.display = "lastline"
vim.o.encoding="utf-8"
-- vim.wo.winblend = 100
-- vim.api.nvim_exec([[set winblend=100]], true)
-- Numbers
vim.wo.number = true
vim.wo.relativenumber = true
vim.wo.cursorline = true
-- vim.wo.cursorcolumn = true
-- Utils
vim.o.compatible = false
vim.o.mouse='a'
vim.o.autoindent = true
vim.o.backup = false
vim.o.undofile = true
vim.o.undodir="~/.vim/undodir"
vim.o.swapfile = false
vim.o.shada="!,'1000,<50,s10,h"
vim.o.viminfo="'100,n$HOME/.vim/files/info/viminfo"
vim.o.clipboard = "unnamedplus"
-- Times
vim.o.ttimeoutlen = 50
vim.o.updatetime = 100
vim.o.shortmess = "I"
vim.o.laststatus = 2
Or Just with opt ( Highly Recommended )
-- Ignorecases
vim.opt.ignorecase = true
vim.opt.smartcase = true
-- Search
vim.opt.hlsearch = true
vim.opt.incsearch = true
vim.opt.inccommand = "split"
-- Vim UI
vim.opt.smartindent = true
vim.opt.wrap = false
vim.opt.colorcolumn = '120'
vim.opt.showcmd = true
vim.opt.tabstop = 4
vim.opt.softtabstop = 4
vim.opt.signcolumn="yes"
vim.opt.scrolloff = 8
vim.opt.showmode = false
vim.opt.completeopt = "menuone,noinsert,noselect"
vim.opt.hidden = true
-- vim.opt.include
vim.opt.display = "lastline"
vim.opt.backspace = "indent,eol,start"
-- Visuals
vim.opt.syntax = "enable"
-- vim.opt.filetype = "plugin on"
vim.opt.t_Co="256"
-- vim.api.nvim_set_optption('t_Co',256)
vim.opt.termguicolors = true
-- vim.opt.encodingvim.o.hidden = true
vim.opt.include = ""
vim.opt.display = "lastline"
vim.opt.encoding="utf-8"
-- vim.opt.winblend = 100
-- vim.api.nvim_exec([[set winblend=100]], true)
-- Numbers
vim.opt.number = true
vim.opt.relativenumber = true
vim.opt.cursorline = true
-- vim.opt.cursorcolumn = true
-- Utils
vim.opt.compatible = false
vim.opt.mouse='a'
vim.opt.autoindent = true
vim.opt.backup = false
vim.opt.undofile = true
vim.opt.undodir="~/.vim/undodir"
vim.opt.swapfile = false
vim.opt.shada="!,'1000,<50,s10,h"
vim.opt.viminfo="'100,n$HOME/.vim/files/info/viminfo"
vim.opt.clipboard = "unnamedplus"
-- Times
vim.opt.ttimeoutlen = 50
vim.opt.updatetime = 100
vim.opt.shortmess = "I"
vim.opt.laststatus = 2
just like that …
The Keymappings
In vimscript we write , the Keymappings like:
set Leader = " "
nnoremap <Leader>tg :echo "tg"<CR>
But in Lua , we can call it like this:
vim.g.mapleader = " "
vim.api.nvim_set_keymap('n', '<Leader>tg', ':echo "tg"<CR>', { noremap = true , silent = false , expr = false})
Tutorial ,
the vim.api.nvim_set_keymap is a Meta-accessors , to make a keymap in lua . Inside , the first parenthesis ()
- ‘n’ , calls for normal mode . same thing for , ‘i’ for insert mode, ’t' for terminal mode
- ‘
tg’ or the second parameter is the trigger that , you are calling to do a task - ‘:echo “tg”
’ or the third parameter is the actual function or task you are gonna call after make the trigger. - The fourth parameter { noremap = true , silent = false , expr = false} is gonna take your accessors to make it is , call
truefor those things which will gonna work while calling the trigger.
call , the first, second and third parameter in quotes.
if any of
noremap, silent, expris false , you don’t need to call it though .
Calling vim command inside Lua
To call vim command in lua, do:
vim.cmd("the vim command ...")
To call a multiline vim command in lua, call:
vim.cmd([[
this is a
multiline
vim
command ...
]])
Autocommands
Neovim still doesn’t support calling Autocommands in lua ,
To achieve autocommand in lua
- first approach
vim.cmd([[
augroup highlight_yank
autocmd!
autocmd TextYankPost * silent! lua require'vim.highlight'.on_yank({timeout = 40})
augroup END
]])
but for some reason, this doesn’t work everytime.
- second approach
vim.api.nvim_exec([[
augroup highlight_yank
autocmd!
autocmd TextYankPost * silent! lua require'vim.highlight'.on_yank({timeout = 40})
augroup END
]], true)
the vim.api.nvim_exec is another Meta-accessor for calling a nvim command inside lua.
you have specify
trueafter the ]], insidevim.api.nvim_exec
Functions, loops, if
To call a function or loops or if inside lua
- first approach
vim.cmd([[
function! GitStatus()
let [a,m,r] = GitGutterGetHunkSummary()
return printf('+%d ~%d -%d', a, m, r)
endfunction
set statusline+=%{GitStatus()}
]])
vim.cmd([[
if (empty($TMUX))
if (has("nvim"))
let $NVIM_TUI_ENABLE_TRUE_COLOR=1
endif
if (has("termguicolors"))
set termguicolors
endif
endif
]])
but for some reason, if this doesn’t work .
- second approach
vim.api.nvim_exec([[
function! GitStatus()
let [a,m,r] = GitGutterGetHunkSummary()
return printf('+%d ~%d -%d', a, m, r)
endfunction
set statusline+=%{GitStatus()}
]], true)
vim.api.nvim_exec([[
if (empty($TMUX))
if (has("nvim"))
let $NVIM_TUI_ENABLE_TRUE_COLOR=1
endif
if (has("termguicolors"))
set termguicolors
endif
endif
]], true)
To Source any .vim file in lua
just call:
vim.cmd("source the/file/path/here.lua")
Some References
- A verbose and full guide for lua Tutorial
- A great talk by Nvim core developer Tj Devries
- List of some plugins written in Lua by rockerBOO
- My Nvim Dotfiles
- The greatest file picker ever existed