Kodėl vis dar Neovim, kai yra VS Code?
Prieš kelerius metus, kai pirmą kartą išbandžiau Neovim, atrodė, kad tai kažkoks masochizmas. Kam man mokytis šimtų klavišų kombinacijų, kai galiu tiesiog atsidaryti VS Code ir pradėti rašyti kodą? Bet štai dabar, po kelių metų kasdienio naudojimo, negaliu įsivaizduoti grįžtančio prie tradicinių IDE.
Neovim – tai ne tiesiog teksto redaktorius. Tai filosofija, kaip turėtų veikti development aplinka. Kai įvaldai vim judesius, kai tavo pirštai automatiškai randa reikiamus klavišus, kai galima viską daryti neatplėšiant rankų nuo pagrindinio klaviatūros bloko – supranti, kodėl žmonės taip fanatiškai gina šį įrankį. Be to, Neovim 0.5 versija ir vėlesnės atnešė Lua konfigūraciją, LSP palaikymą ir modernias funkcijas, kurios anksčiau buvo prieinamos tik dideliuose IDE.
Bet čia ne straipsnis apie tai, kodėl Neovim yra geras. Čia praktinis vadovas, kaip jį sukonfigūruoti taip, kad jis taptų pilnaverčia development aplinka 2024 metais.
Pagrindų pagrindai: init.lua struktūra
Seniau Neovim konfigūracija buvo rašoma Vimscript kalba, kuri, būkime sąžiningi, nėra pati maloniausiai skaitoma. Dabar viskas keičiasi su Lua. Tai greita, paprasta ir labai logiška kalba.
Pirmiausia reikia sukurti tinkamą katalogų struktūrą. Mano konfigūracija gyvena `~/.config/nvim/` kataloge (Linux/Mac) arba `~/AppData/Local/nvim/` (Windows). Štai kaip aš organizuoju failus:
„`
~/.config/nvim/
├── init.lua
├── lua/
│ ├── core/
│ │ ├── options.lua
│ │ ├── keymaps.lua
│ │ └── autocmds.lua
│ ├── plugins/
│ │ ├── init.lua
│ │ ├── lsp.lua
│ │ ├── treesitter.lua
│ │ └── telescope.lua
│ └── utils/
│ └── helpers.lua
„`
Pagrindinis `init.lua` failas yra labai paprastas – jis tiesiog įkelia kitus modulius:
„`lua
require(„core.options”)
require(„core.keymaps”)
require(„core.autocmds”)
require(„plugins”)
„`
Tokia struktūra leidžia lengvai naršyti po konfigūraciją ir greitai rasti, ką reikia pakeisti. Kai viskas sukrauta į vieną failą, po kelių mėnesių pats nebesuprantai, kas už ką atsakinga.
Plugin manager’is: lazy.nvim dominacija
Ilgą laiką naudojau packer.nvim, bet lazy.nvim tiesiog išsprogdino galvas. Šis plugin manager’is ne tik greitesnis, bet ir turi daug protingų funkcijų: lazy loading, automatinį dependency valdymą, gražią UI ir dar daugybę smulkmenų.
Įdiegti lazy.nvim galima viena komanda `init.lua` faile:
„`lua
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”,
lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
„`
Dabar galima apibrėžti plugins. Aš tai darau atskirame `lua/plugins/init.lua` faile:
„`lua
require(„lazy”).setup({
— Čia bus mūsų pluginai
spec = {
{ import = „plugins.lsp” },
{ import = „plugins.treesitter” },
{ import = „plugins.telescope” },
},
performance = {
cache = {
enabled = true,
},
},
})
„`
Lazy.nvim automatiškai užkrauna tik tuos pluginus, kurių reikia. Jei turite pluginą, kuris reikalingas tik Python failams – jis nebus užkrautas, kol neatidarysite Python failo. Tai drastiškai pagreitina Neovim paleidimą.
LSP konfigūracija: IDE funkcionalumas be IDE
Language Server Protocol – tai technologija, kuri leidžia Neovim turėti visas tas fancy funkcijas, kurias matote VS Code: autocomplete, go to definition, error checking ir t.t. Ir tai veikia ne blogiau, o kartais net geriau.
Pagrindinis pluginas čia yra `nvim-lspconfig`. Bet man patinka naudoti `mason.nvim` kartu su juo – tai leidžia lengvai įdiegti ir valdyti language serverius:
„`lua
return {
{
„williamboman/mason.nvim”,
config = function()
require(„mason”).setup()
end,
},
{
„williamboman/mason-lspconfig.nvim”,
dependencies = { „mason.nvim” },
config = function()
require(„mason-lspconfig”).setup({
ensure_installed = {
„lua_ls”,
„rust_analyzer”,
„tsserver”,
„pyright”,
},
})
end,
},
{
„neovim/nvim-lspconfig”,
dependencies = { „mason-lspconfig.nvim” },
config = function()
local lspconfig = require(„lspconfig”)
local capabilities = require(„cmp_nvim_lsp”).default_capabilities()
— TypeScript
lspconfig.tsserver.setup({
capabilities = capabilities,
})
— Python
lspconfig.pyright.setup({
capabilities = capabilities,
settings = {
python = {
analysis = {
typeCheckingMode = „basic”,
},
},
},
})
end,
},
}
„`
Svarbu suprasti, kad kiekvienam language serveriui galima nustatyti savo settings. Pavyzdžiui, Pyright’ui galiu pasakyti, kokio griežtumo type checking noriu, o Rust Analyzer’iui – kokius cargo features naudoti.
Keymaps LSP funkcijoms aš nusistatau per autocmd, kad jie būtų aktyvūs tik tuose bufferuose, kur LSP veikia:
„`lua
vim.api.nvim_create_autocmd(„LspAttach”, {
group = vim.api.nvim_create_augroup(„UserLspConfig”, {}),
callback = function(ev)
local opts = { buffer = ev.buf }
vim.keymap.set(„n”, „gd”, vim.lsp.buf.definition, opts)
vim.keymap.set(„n”, „K”, vim.lsp.buf.hover, opts)
vim.keymap.set(„n”, „
vim.keymap.set(„n”, „
end,
})
„`
Autocomplete sistema su nvim-cmp
Autocomplete – tai funkcija, be kurios šiuolaikinis development tiesiog neįsivaizduojamas. Neovim ekosistemoje dominuoja `nvim-cmp` – labai galingas ir konfigūruojamas completion engine’as.
Štai mano setup’as:
„`lua
return {
„hrsh7th/nvim-cmp”,
dependencies = {
„hrsh7th/cmp-nvim-lsp”,
„hrsh7th/cmp-buffer”,
„hrsh7th/cmp-path”,
„L3MON4D3/LuaSnip”,
„saadparwaiz1/cmp_luasnip”,
},
config = function()
local cmp = require(„cmp”)
local luasnip = require(„luasnip”)
cmp.setup({
snippet = {
expand = function(args)
luasnip.lsp_expand(args.body)
end,
},
mapping = cmp.mapping.preset.insert({
[„
[„
[„
[„
[„
if cmp.visible() then
cmp.select_next_item()
elseif luasnip.expand_or_jumpable() then
luasnip.expand_or_jump()
else
fallback()
end
end, { „i”, „s” }),
}),
sources = cmp.config.sources({
{ name = „nvim_lsp” },
{ name = „luasnip” },
{ name = „buffer” },
{ name = „path” },
}),
})
end,
}
„`
Čia vyksta daug dalykų. Pirma, apibrėžiame sources – iš kur completion suggestions turėtų ateiti. LSP yra prioritetas, bet taip pat naudojame snippets, buffer turinį ir file paths. Antra, nustatome keymaps – kaip naršyti per suggestions, kaip patvirtinti, kaip atidaryti completion menu.
Vienas trikų, kurį išmokau – galima skirtingiems filetypes naudoti skirtingus sources. Pavyzdžiui, markdown failuose man nereikia LSP completion, bet noriu spelling suggestions.
Treesitter: sintaksės highlighting steroidas
Treesitter – tai technologija, kuri supranta kodo struktūrą, o ne tik spalvina žodžius pagal regex’us. Tai leidžia turėti daug tikslesnius highlights, text objects, ir net kodo manipuliavimo funkcijas.
„`lua
return {
„nvim-treesitter/nvim-treesitter”,
build = „:TSUpdate”,
config = function()
require(„nvim-treesitter.configs”).setup({
ensure_installed = {
„lua”,
„vim”,
„javascript”,
„typescript”,
„python”,
„rust”,
„html”,
„css”,
},
highlight = {
enable = true,
additional_vim_regex_highlighting = false,
},
indent = {
enable = true,
},
incremental_selection = {
enable = true,
keymaps = {
init_selection = „
node_incremental = „
node_decremental = „
},
},
})
end,
}
„`
Incremental selection – tai killer feature. Galiu paspausti Enter ir automatiškai pažymėti dabartinį syntax node (pvz., funkciją), dar kartą Enter – pažymės didesnį node (pvz., klasę), ir taip toliau. Tai neįtikėtinai pagreitina kodo refactoring’ą.
Dar vienas cool dalykas – `nvim-treesitter-textobjects` pluginas, kuris leidžia operuoti su kodo struktūromis kaip su vim text objects. Galiu parašyti `daf` (delete a function) ir ištrinti visą funkciją, kurioje yra kursorius. Arba `vac` (visual a class) – pažymėti visą klasę.
Telescope: fuzzy finder, kuris keičia viską
Telescope – tai turbūt vienas iš svarbiausių pluginų moderniam Neovim setup’ui. Tai fuzzy finder, kuris leidžia greitai rasti failus, tekstą failuose, git commits, LSP symbols ir dar milijoną kitų dalykų.
„`lua
return {
„nvim-telescope/telescope.nvim”,
dependencies = {
„nvim-lua/plenary.nvim”,
{ „nvim-telescope/telescope-fzf-native.nvim”, build = „make” },
},
config = function()
local telescope = require(„telescope”)
local actions = require(„telescope.actions”)
telescope.setup({
defaults = {
mappings = {
i = {
[„
[„
},
},
file_ignore_patterns = {
„node_modules”,
„.git/”,
„dist/”,
„build/”,
},
},
})
telescope.load_extension(„fzf”)
end,
}
„`
Mano dažniausiai naudojami Telescope keymaps:
„`lua
vim.keymap.set(„n”, „
vim.keymap.set(„n”, „
vim.keymap.set(„n”, „
vim.keymap.set(„n”, „
vim.keymap.set(„n”, „
„`
`fzf-native` extension’as labai svarbus – jis naudoja natyvų fzf algoritmą, kuris yra daug greitesnis už default Lua implementaciją. Dideliuose projektuose skirtumas akivaizdus.
Vienas patarimas – naudokite `file_ignore_patterns`, kad Telescope neindeksuotų node_modules ar kitų didelių katalogų. Tai labai pagreitina search’ą.
Git integracija ir debugging
Development be git – tai kaip vairuoti be vairo. Neovim turi puikių git pluginų, ir mano favoritas yra `gitsigns.nvim`:
„`lua
return {
„lewis6991/gitsigns.nvim”,
config = function()
require(„gitsigns”).setup({
signs = {
add = { text = „│” },
change = { text = „│” },
delete = { text = „_” },
topdelete = { text = „‾” },
changedelete = { text = „~” },
},
on_attach = function(bufnr)
local gs = package.loaded.gitsigns
vim.keymap.set(„n”, „]c”, function()
if vim.wo.diff then return „]c” end
vim.schedule(function() gs.next_hunk() end)
return „
end, { expr = true, buffer = bufnr })
vim.keymap.set(„n”, „[c”, function()
if vim.wo.diff then return „[c” end
vim.schedule(function() gs.prev_hunk() end)
return „
end, { expr = true, buffer = bufnr })
vim.keymap.set(„n”, „
vim.keymap.set(„n”, „
vim.keymap.set(„n”, „
end,
})
end,
}
„`
Gitsigns rodo pakeitimus šalia line numbers, leidžia stage/unstage individual hunks, ir turi daugybę kitų funkcijų. Galiu greitai peršokti tarp pakeitimų su `]c` ir `[c`, preview’inti pakeitimus su `
Dėl debugging – `nvim-dap` (Debug Adapter Protocol) yra standartinis sprendimas. Setup’as šiek tiek sudėtingesnis, nes reikia konfigūruoti kiekvienai kalbai atskirai, bet rezultatas vertas:
„`lua
return {
„mfussenegger/nvim-dap”,
dependencies = {
„rcarriga/nvim-dap-ui”,
„theHamsta/nvim-dap-virtual-text”,
},
config = function()
local dap = require(„dap”)
local dapui = require(„dapui”)
dapui.setup()
— Automatiškai atidaryti/uždaryti DAP UI
dap.listeners.after.event_initialized[„dapui_config”] = function()
dapui.open()
end
dap.listeners.before.event_terminated[„dapui_config”] = function()
dapui.close()
end
vim.keymap.set(„n”, „
vim.keymap.set(„n”, „
vim.keymap.set(„n”, „
vim.keymap.set(„n”, „
vim.keymap.set(„n”, „
end,
}
„`
Kaip tai viskas veikia kartu ir ko tikėtis
Po kelių savaičių naudojant tokį setup’ą, pradedi suprasti, kodėl žmonės taip myli Neovim. Viskas veikia greitai – paleidimas užtrunka mažiau nei sekundę, LSP atsako akimirksniu, Telescope randa failus greičiau nei spėji pagalvoti.
Bet svarbiausias dalykas – tai jūsų konfigūracija. Neovim nėra „vieno dydžio visiems” sprendimas. Kas man veikia, jums gali būti per sudėtinga arba per paprasta. Pradėkite nuo basic setup’o, pridėkite pluginus po vieną, išmokite juos naudoti, ir tik tada pridėkite naujų.
Mano patarimas – nesistenkite nukopijuoti kažkieno „ultimate Neovim config” iš GitHub. Pradėkite nuo tuščio `init.lua`, pridėkite plugin manager’į, tada LSP, tada completion, ir taip toliau. Kiekvienas pluginas, kurį pridėdate, turėtų spręsti konkrečią jūsų problemą, o ne būti ten „nes visi taip daro”.
Neovim konfigūracija – tai niekada nesibaigianti kelionė. Aš vis dar kas savaitę randu naujų pluginų, naujų workflow’ų, naujų būdų optimizuoti savo darbą. Ir tai yra gražu – jūsų editor’ius auga kartu su jumis, prisitaiko prie jūsų poreikių, tampa tikrai jūsų įrankiu.
Taip pat nepamirškite, kad Neovim community yra neįtikėtinai aktyvi ir draugiška. Jei kažkas neveikia, jei nežinote, kaip kažką padaryti – klauskite. Reddit’o r/neovim, Discord serveriai, GitHub discussions – visur rasite žmonių, kurie mielai padės.
Ir paskutinis dalykas – nepasiduokite per greitai. Pirmą savaitę bus sunku, pirštai klaidžios ne tuos klavišus, norėsis grįžti prie senojo IDE. Bet ištvermingumas atsipirks. Po mėnesio jau negalėsite įsivaizduoti, kaip gyvenote be modal editing. Po trijų – jūsų produktyvumas išaugs taip, kad patys nustebsite. O po metų – bandysite įdiegti vim keybindings į kiekvieną programą, kurią naudojate.
