About Vim ” Migrating to Neovim (Lua)


https://oir.mobi/uploads/posts/2019-12/thumbs/1575937316_10-17.jpg

Introduction

Theoretically, if you decide to switch from classic Vim to its more modern clone – Neovim – you don’t need to do anything special. In file ~/.config/nvim/init.vim prescribe source ~/.vimrc Well, download or copy dictionaries. The idea is that Neovim should support all default Vim configurations. However, if you have a lot of plugins installed and different extensions for them, then with a high probability the configuration will load with errors, warnings, and other, not very desirable nuances. And in general, the whole point, the whole difference of Neovim lies in the fact that it supports settings and plugins written in Lua instead of vimscript.

Lua is a more modern interpreted language, easier to write and easier to read. And yet, it is believed that the interpreter works an order of magnitude faster than the native language. As for the order, I would doubt it, but really heavy plugins seem to work faster and smoother. However, the classic Vim is not known for being slow or buggy, so the argument here is rather sophistical. But I absolutely agree with the first three statements.

Moreover, for Neovim, it is on Lua that the choice of modern plug-ins and extensions has recently been, what can I say, much richer. The question is not even whether these analogues are better, but rather in the freshness, dynamics of development and, in general, in the optimism of the community. It is very likely that if not today, then tomorrow Neovim will repeat the fate of its predecessor and take its place in the mainstream Linux distributions as a standard replacement for the obsolete Vim. Replace completely? Well, I would not be so categorical in this matter, but such a possibility exists.

On the other hand, the philosophy of Neovim does not have this old asceticism and supposed super-reliability. To work in exclusive mode in the console of a network device, the presence of Neovim is still not expected. And the developers are implicitly counting on a certain device on which you can find both a set of modern runtime environments and an Internet connection. Thus, configuring Neovim is not a task to keep working in a stripped-down and minimalist environment.

In this regard, the network now has a dozen – no less, high-quality prefabricated hodgepodges – ready-made settings, by setting which you get a harvester ready for most use cases out of the box. In fact, these are such separate products with their own brands, with their own community of fans and regular users. And if you are not a fan of digging into the finer settings of programs on your own, then one of the brands will most likely suit you by 90%.

However, as you understand, I am one of those citizens who need the remaining 10%, so I will not save it. And if you are one of those people who are ready to teach Neovim something very personal or out of the box, then the rest of the article is for you.

Reset

Since the material presented is such an experiment on oneself, about how much it makes sense to go all the way from a bare editor to a full-fledged IDE, then I will do it simply – I will start from scratch. I already have a map of which direction to go – I’ll just repeat the whole path done with the original vimscript, but now under Lua.

Before the reset, however, you need to prepare a little. First, I should attach a list of Vim articles from earlier.

Introductory to the cycle

Hotkeys

Insert mode

Files and plugins

built-in

DB Client

Formatting

PHP LSP

JDT LS

Secondly, I will add to the environment the settings of the tools I am used to – tmux, zsh + oh-my-zh, some aliases on GitHub. There, in principle, almost everything is standard, so you can safely download them.

Pro Vim dotfiles

Thirdly, it must be noted that the approach to organizing configuration files in Lua is very different from the approach in vimscript. Everything is classic here: modules, hierarchy, nesting. That is, we immediately abandon the linear structure and put everything on the shelves.

The main file – the entry point in this case is ~/.config/nvim/init.lua. Create one if it does not exist and delete the previous one ~/.config/nvim/init.vimif you had it.

basic settings

To make it easier to manage the settings in the future, it is recommended to place them in a special directory lua and separate the main settings and plugin settings into different directories.

$ cd ~/.config/nvim
$ mkdir lua
$ mkdir lua/core lua/plugins

You can divide the meaning and basic settings into certain categories.

$ cd lua/core
$ touch options.lua keymaps.lua colorscheme.lua

I got this from someone. This is not required at all, but it looks reasonable. Next, just paste in ~/.config/nvim/init.lua (I’ll refer to it as the “main file” from now on) referencing these files according to the Lua syntax and paradigm, where dots on lines are interpreted as directories and an extension is added at the end .lua.

require("core.options")
require("core.keymaps")
require("core.colorscheme")

For programmers, it should be noted that the syntax of Lua is quite peculiar and very variable, you can do one thing in ten different ways. Which is sometimes annoying when something just refuses to work for no apparent reason. And sometimes it helps when something works despite small typos. Let’s just say, even more liberal JavaScript. Each file in this case is not a macro, but a function with the result of execution. For those who are not alien to vimscript, you can read Here about how to quickly get along with Lua.

Options

First of all, I will transfer the global settings to options.luaso that we would not continue to work in a bare editor.

local opt = vim.opt

-- line number
opt.relativenumber = true
opt.number = true

-- tabs & indentation
opt.tabstop = 4 -- 4 spaces for tabs (prettier default)
opt.shiftwidth = 4 -- 4 spaces for indent width
opt.expandtab = true -- expand tab to spaces
opt.autoindent = true -- copy indent from current line when starting new one

-- line wrapping
opt.wrap = true -- disable line wrapping
opt.linebreak = true

-- search settings
opt.ignorecase = true -- ignore case when searching
opt.smartcase = true -- if you include mixed case in your search, assumes you want case-sensitive

-- cursor line
opt.cursorline = true -- highlight the current cursor line

-- appearance

-- turn on termguicolors for nightfly colorscheme to work
-- (have to use iterm2 or any other true color terminal)
opt.termguicolors = true
opt.background = "dark" -- colorschemes that can be light or dark will be made dark
opt.signcolumn = "yes" -- show sign column so that text doesn't shift

-- backspace
opt.backspace = "indent,eol,start" -- allow backspace on indent, end of line or insert mode start position

-- clipboard
opt.clipboard:append("unnamedplus") -- use system clipboard as default register

-- split windows
opt.splitright = true -- split vertical window to the right
opt.splitbelow = true -- split horizontal window to the bottom

opt.iskeyword:append("-") -- consider string-string as whole word

-- spelling
opt.spelllang = { "en_us", "ru" } -- Словари рус eng
opt.spell = true

-- redundancy
opt.undofile = true --  keep undo history between sessions
opt.undodir = "~/.vim/undo/" -- keep undo files out of file dir
opt.directory = "~/.vim/swp/" -- keep unsaved changes away from file dir
opt.backupdir = "~/.vim/backup/" -- backups also should not go to git

Explanations for the settings are given both in the comments and in the first article of the cycle. We will not dwell on them for a long time. I will only add that in Lua it is customary to shorten commands by creating variable aliases of certain objects. In this case, it’s the first line in the script. And also, it will not be superfluous for programmers to understand that the assignment operator here is actually an abbreviation for the function vim.api.nvim_win_set_option. What you can read in the manual, on GitHub or on the project page lua-guide.

Restart or resource.

:so $MYVIMRC

color scheme

To file colorscheme.lua let’s put the following:

-- Color control
vim.g.sonokai_style = "andromeda"
vim.g.sonokai_better_performance = 1

vim.g.sonokai_transparent_background = 1
vim.g.sonokai_diagnostic_text_highlight = 1

-- set colorscheme with protected call
-- in case it isn't installed
local status, _ = pcall(vim.cmd, "colorscheme sonokai")
if not status then
	print("Colorscheme not found, defaulting to builtin")
    vim.cmd([[colorscheme desert]])
	return
end

Why couldn’t you just install the schema? Yes it was possible. This piece is more for demonstration purposes. It shows, for example, that there is an option to call the command in “safe” mode and handle the absence of the corresponding color scheme. Moreover, the second unconditional call to the built-in scheme colorscheme desert implemented as a native vimscript command. That is, if there is no easy way to execute a command from Lua, then you can use the syntax with two square brackets like this, which is also multiline.

It must also be understood that this is all a real configuration, and not the direct execution of commands. Here we set what will then be performed when this function is called. That is, at first the configuration is completely formed, and then it is executed when calling require.

You can separately call a function from a file with the command :luafile or :source:

:luafile ~/.config/nvim/lua/core/colorscheme.lua

Keymaps

And finally, we should return to the usual keyboard shortcuts.

-- set leader key to space
vim.g.mapleader = " "

local keymap = vim.keymap -- for conciseness

local cmd = vim.cmd

-- Русский язык

cmd("set keymap=russian-jcukenwin")
cmd("set iminsert=0")
cmd("set imsearch=0")

---------------------
-- General Keymaps
---------------------

-- soft wrap remap
local expr_opts = { noremap = true, expr = true, silent = true }
keymap.set({ "n", "v" }, "j", "v:count == 0 ? 'gj' : 'j'", expr_opts)
keymap.set({ "n", "v" }, "k", "v:count == 0 ? 'gk' : 'k'", expr_opts)
keymap.set({ "n", "v" }, "<Down>", "v:count == 0 ? 'gj' : 'j'", expr_opts)
keymap.set({ "n", "v" }, "<Up>", "v:count == 0 ? 'gk' : 'k'", expr_opts)

-- use jk to exit insert mode
keymap.set({ "v", "i" }, "\\'", "<ESC>")
keymap.set({ "v", "i" }, "\\э", "<ESC>")

-- unbind ins toggle
keymap.set("i", "<Ins>", "<ESC>")

-- yank and paste clipboard
--keymap.set({ "n", "v", "x", "t" }, "<leader>y", '"+y')
--keymap.set({ "n", "v", "x", "t" }, "<leader>Y", '"*y')
--keymap.set({ "n", "t" }, "<leader>p", '"+p')
--keymap.set({ "n", "t" }, "<leader>P", '"*p')

-- do not yank on replace or delete
--keymap.set({ "v", "x" }, "<leader>p", '"_d"+p')
--keymap.set({ "v", "x" }, "<leader>P", '"_d"*p')
--keymap.set({ "n", "v", "x", "t" }, "<leader>d", '"_d')

-- copy default reg to/from system/mouse clipboard
keymap.set({"n", "v", "x"}, '<Leader>y', ':let @+=@"<CR>')
keymap.set({"n", "v", "x"}, '<Leader>p', ':let @"=@+<CR>')
keymap.set({"n", "v", "x"}, '<Leader>Y', ':let @*=@"<CR>')
keymap.set({"n", "v", "x"}, '<Leader>P', ':let @"=@*<CR>')


-- clear search highlights
keymap.set("n", "<leader>nh", ":nohl<CR>")

-- delete single character without copying into register
keymap.set("n", "x", '"_x')

-- increment/decrement numbers
keymap.set("n", "<leader>+", "<C-a>") -- increment
keymap.set("n", "<leader>-", "<C-x>") -- decrement

-- navigate windows
keymap.set("n", "<C-j>", "<C-w>j")
keymap.set("n", "<C-h>", "<C-w>h")
keymap.set("n", "<C-k>", "<C-w>k")
keymap.set("n", "<C-l>", "<C-w>l")

-- move out of terminal
keymap.set("t", "<C-j>", "<C-\\><C-n><C-w>j")
keymap.set("t", "<C-k>", "<C-\\><C-n><C-w>k")
keymap.set("t", "<C-h>", "<C-\\><C-n><C-w>h")
keymap.set("t", "<C-l>", "<C-\\><C-n><C-w>l")

-- move line or v-block
keymap.set("i", "<C-j>", "<Esc><cmd>m .+1<CR>==gi")
keymap.set("i", "<C-k>", "<Esc><cmd>m .-2<CR>==gi")
keymap.set("x", "J", ":m '>+1<CR>gv-gv", { noremap = true, silent = true })
keymap.set("x", "K", ":m '<-2<CR>gv-gv", { noremap = true, silent = true })

-- stay in indent mode
keymap.set("v", ">", ">gv", { noremap = true, silent = true })
keymap.set("v", "<", "<gv", { noremap = true, silent = true })

-- window management
keymap.set("n", "<leader>wv", "<C-w>v") -- split window vertically
keymap.set("n", "<leader>wh", "<C-w>s") -- split window horizontally
keymap.set("n", "<leader>we", "<C-w>=") -- make split windows equal width & height
keymap.set("n", "<leader>wx", ":close<CR>") -- close current split window

-- tabs
keymap.set("n", "<leader>to", ":tabnew<CR>") -- open new tab
keymap.set("n", "<leader>tx", ":tabclose<CR>") -- close current tab
keymap.set("n", "<leader>tn", ":tabn<CR>") --  go to next tab
keymap.set("n", "<leader>tp", ":tabp<CR>") --  go to previous tab

-- buffers
keymap.set("n", "<leader>bo", ":new<CR>") -- open new tab
keymap.set("n", "<leader>bd", ":bdelete<CR>") -- close current tab
keymap.set("n", "<leader>bn", ":bn<CR>") --  go to next tab
keymap.set("n", "<leader>bp", ":bp<CR>") --  go to previous tab

-- shift arrow like gui
keymap.set("n", "<S-Up>", "v<Up>")
keymap.set("n", "<S-Down>", "v<Down>")
keymap.set("n", "<S-Left>", "v<Left>")
keymap.set("n", "<S-Right>", "v<Right>")
keymap.set("v", "<S-Up>", "<Up>")
keymap.set("v", "<S-Down>", "<Down>")
keymap.set("v", "<S-Left>", "<Left>")
keymap.set("v", "<S-Right>", "<Right>")
keymap.set("i", "<S-Up>", "<Esc>v<Up>")
keymap.set("i", "<S-Down>", "<Esc>v<Down>")
keymap.set("i", "<S-Left>", "<Esc>v<Left>")
keymap.set("i", "<S-Right>", "<Esc>v<Right>")

-- copy paste like gui
keymap.set("v", "<C-c>", '"+y<Esc>i')
keymap.set("v", "<C-x>", '"+d<Esc>i')
keymap.set("i", "<C-v>", '"+pi')
keymap.set("i", "<C-v>", '<Esc>"+pi', { noremap = true, silent = true })
keymap.set("i", "<C-z>", "<Esc>ui", { noremap = true, silent = true })
keymap.set("i", "<C-z>", "<Esc>ui", { noremap = true, silent = true })
keymap.set({ "i", "v", "x", "t" }, "<C-a>", "<C-\\><C-n>ggVG", { noremap = true, silent = true })

----------------------
-- Plugin Keybinds
----------------------

Here I deliberately left some comments. At the end, a blank for plugin combinations. Please note that now they can be placed in one place, regardless of where and when the plugin itself is connected. Also, if you pay attention to the register usage section, then here I disabled the popular way of working with the system clipboard and presented my find.

This approach differs in that we do not touch the behavior of standard keys, but supplement them with operations to move the register to the buffer and back. Thus, the movements associated with the use of keys are not disturbed. y And p, and work with the external buffer goes as an additional command. For my taste, this approach turned out to be more intuitive, although one more minor step is added. It also allows you to then overwrite the registers as many times as you like without overwriting the system buffer when inserting the same one into several places with overwriting.

Probably enough for today. Next are plugins.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *