Предисловие
В процессе знакомства с Neovim было прочитано много статей, конфигураций на Github, было просмотрено огромное количество роликов на Youtube на тему настройки, но в большинстве случаев приходилось донастраивать все под себя. В этой статье я расскажу как я настроил Neovim для разработки на Go, используя только Lua плагины и init.lua.
Эта статья может быть полезна тебе, если:
-
пишешь на Go
-
есть конфиг на Vimscript, но хочется на Lua
-
хочется пересесть с тяжелых современных IDE или текстовых редакторов, таких как Goland, Vscode и других, на Neovim
Vimscript против Lua
Подробного их сравнения в этой статье не будет, так как это выходит за ее рамки, но основные преимущества Lua перед Vimscript это:
-
Скорость. Плагины написанные на Lua будут работать быстрее чем их реализации на Vimscript
-
Модульность. С Lua ты сможешь навести порядок — горячие клавиши в одном файле, настройки в другом, плагины в третьем т.д.
-
Простота и поддерживаемость. Vimscript очень специфичен и используется только внутри Vim. Lua же в свое время является одним из самых популярных встраиваемых скриптовых языков за счет свой скорости и простоты, например, на нем написан интерфейс World of Warcraft и на нем пишут плагины для Kong API Gateway
Структура конфига
Lua позволяет разбирать настройки по файлам (модулям), сделать это можно сделать следующим образом:
├── init.lua └── lua ├── autocommands.lua ├── keymaps.lua ├── lsp.lua ├── options.lua └── plugins ├── init.lua
-
init.lua— инициализирует модули из директории lua, файлы init инициализируются по умолчанию, для них не нужно прописыватьrequire('init.lua') -
lua/options.lua— основные настройки Neovim -
lua/keymaps.lua— горячие клавиши -
lua/lsp.lua— конфигурация Language Server Protocol для автодополнения и поддержки языков, как в твоем любимом IDE -
lua/autocommands.lua— автокоманды, например, сортировка импортов после сохранения -
lua/plugins/init.lua— инициализация плагинов; конфигурацию для конкретных плагинов я храню вlua/plugins/<plugin_name>.lua
После этого, чтобы подгрузились все модули, необходимо инициализировать их в init.lua:
-- ~/.config/nvim/init.lua require('options') require('keymaps') require('autocommands') require('plugins') require('lsp')
При инициализации модулей порядок важен, поэтому не стоит, например, в модуле keymaps держать горячие клавиши для плагинов, а лучше переместить их в конкретный модуль в lua/plugins/<plugin_name>.lua
Плагин менеджер
Для установки плагинов для Lua конфигураций можно использовать плагин менеджер Packer.nvim — Lua альтернатива для популярного vim-plug.
Базовая конфигурация Packer:
-- ~/.config/nvim/lua/plugins/init.lua vim.cmd [[packadd packer.nvim]] return require('packer').startup(function(use) use 'wbthomason/packer.nvim' -- ... end)
Если нужно, чтобы Packer автоматически устанавливался на любом ПК, можно использовать следующий сниппет:
-- ~/.config/nvim/lua/plugins/init.lua local fn = vim.fn local install_path = fn.stdpath('data')..'/site/pack/packer/start/packer.nvim' if fn.empty(fn.glob(install_path)) > 0 then packer_bootstrap = fn.system({'git', 'clone', '--depth', '1', 'https://github.com/wbthomason/packer.nvim', install_path}) end return require('packer').startup(function(use) use 'wbthomason/packer.nvim' -- ... if packer_bootstrap then require('packer').sync() end end)
Установка плагинов
Для установки и обновления плагинов можно использовать команду :PackerSync.
Обязательные плагины
Для упрощенной настройки LSP, автодополнений и навигации по файлам были собраны наиболее популярные плагины в сообществе и приведены ниже:
-- ~/.config/nvim/lua/plugins/init.lua -- ... return require('packer').startup(function(use) use 'wbthomason/packer.nvim' -- набор Lua функций, используется как зависимость в большинстве -- плагинов, где есть работа с асинхронщиной use 'nvim-lua/plenary.nvim' -- конфиги для LSP серверов, нужен для простой настройки и -- возможности добавления новых серверов use 'neovim/nvim-lspconfig' -- зависимости для движка автодополнения use 'hrsh7th/cmp-nvim-lsp' use 'hrsh7th/cmp-buffer' use 'hrsh7th/cmp-path' -- движок автодополнения для LSP use 'hrsh7th/nvim-cmp' -- парсер для всех языков программирования, цветной код как в твоем -- любимом IDE use { 'nvim-treesitter/nvim-treesitter', run = ':TSUpdate', config = function() -- так подгружается конфигурация конкретного плагина -- ~/.config/nvim/lua/plugins/treesitter.lua require('plugins.treesitter') end } -- навигация по файлам, fzf, работа с буферами и многое другое -- если больше привыкли к файловому дереву, есть альтернатива - nvim-tree -- https://github.com/kyazdani42/nvim-tree.lua use { 'nvim-telescope/telescope.nvim', config = function() require('plugins.telescope') end } end)
Дополнительные плагины
Есть ряд плагинов, которые ускорят разработку, они в большей степени заменяют функционал, к которому большинство привыкло в современных IDE и редакторах, а некоторых из них «улучшают» внешний вид Neovim, если вам это необходимо:
-- ~/.config/nvim/lua/plugins/init.lua -- ... return require('packer').startup(function(use) -- плагины из предыдущего абзаца -- Иконки для расширений файлов (для корректной работы нужен -- установленный один из Nerd шрифтов в терминале) - опционален -- https://github.com/ryanoasis/nerd-fonts use { 'kyazdani42/nvim-web-devicons', config = function() require('nvim-web-devicons').setup({ default = true; }) end } -- ИИ автодополнения use { 'tzachar/cmp-tabnine', run='./install.sh' } -- иконки в выпадающем списке автодополнений (прямо как в vscode) use('onsails/lspkind-nvim') -- статусбар, аналог vim-airline, только написан на lua use { 'nvim-lualine/lualine.nvim', config = function() require('plugins.lualine') end } -- отображение буфферов/табов в верхнем горизонтальном меню -- p.s. сам не использую, мне хватает telescope use { 'akinsho/bufferline.nvim', tag = "v2.*", config = function() require('plugins.bufferline') end } -- движок сниппетов use { 'L3MON4D3/LuaSnip', after = 'friendly-snippets', config = function() require('luasnip/loaders/from_vscode').load({ paths = { '~/.local/share/nvim/site/pack/packer/start/friendly-snippets' } }) end } -- автодополнения для сниппетов use 'saadparwaiz1/cmp_luasnip' -- набор готовых сниппетов для всех языков, включая go use 'rafamadriz/friendly-snippets' -- плагин для простого комментирования кода use { 'numToStr/Comment.nvim', config = function() require('Comment').setup() end } -- автоматические закрывающиеся скобки use { 'windwp/nvim-autopairs', config = function() require("nvim-autopairs").setup() end } -- набор утилит для Go use { 'olexsmir/gopher.nvim', config = function() require('plugins.gopher') end } -- ... end)
P.S. Стоит заметить, что если в ~/config/nvim/lua/plugins/init.lua у плагинов прописано поле config с подгрузкой модуля (как в конфигурации выше), но этого модуля не существует, или для описанного плагина не был вызван setup(), плагин не будет подгружен или при входе в Neovim всплывет ошибка.
Плагины для Go
Есть множество вариантов использования Neovim как IDE для Go. Например можно использовать coc.nvim — альтернатива нативному LSP с еще более простой настройкой и поддержкой множества серверов, но меньшей скоростью, так как написан он на nodejs.
Также можно использовать vim-go, который имеет поддержку gopls LSP и поддержку языка Go, что позволяет не выходят из редактора выполнять такие команды, как GoInstall, GoBuild и другие. Но не смотря на наличие таких целостных решений я решил пойти другим путем и использовать максимально модульные и простые решения, остановившись на нативном LSP и плагине gopher.nvim.
-
нативный LSP — позволяет настроить автодополнения, диагностику, форматирование (настройка LSP будет рассмотрена чуть позже)
-
gopher.nvim — набор утилит для Go (например позволяет в одну команду добавить/удалить теги к структуре или сгенерировать табличные тесты, полный список команд здесь)
Устанавливается gopher аналогично с другими плагинами:
-- ~/.config/nvim/lua/plugins/init.lua -- ... return require('packer').startup(function(use) -- плагины из предыдущего абзаца use { 'olexsmir/gopher.nvim', config = function() require('plugins.gopher') end } -- ... end)
Настройка плагина gopher будет приведена в абзаце «Настройка LSP и плагинов».
Настройка LSP и плагинов
Подсветка синтаксиса и навигация
-
nvim-treesitter — подсветка синтаксиса на основе парсеров tree-sitter
-- ~/.config/nvim/lua/plugins/treesitter.lua require('nvim-treesitter.configs').setup{ -- список парсеров, список доступных парсеров можно посмотреть в документации -- либо устаналивать все, чтобы подсветка синтаксиса работала везде корректно -- https://github.com/nvim-treesitter/nvim-treesitter ensure_installed = 'all', -- установка phpdoc падает на m1 ignore_install = { 'phpdoc' }, -- включить подсветку highlight = { enable = true } }
-
telescope — навигация по фалам, буферам, греп и многое другое
-- ~/.config/nvim/lua/plugins/telescope.lua require('telescope').setup { pickers = { buffers = { -- начинать в normal моде при открытии списка буферов initial_mode = 'normal' } } } local map = vim.api.nvim_set_keymap local opts = {noremap = true, silent = true} -- горячие клавиши map('n', '<leader>ff', '<cmd>Telescope find_files<CR>', opts) map('n', '<leader>fg', '<cmd>Telescope live_grep<CR>', opts) map('n', '<leader>fb', '<cmd>Telescope buffers<CR>', opts)
LSP
Для корректной работы LSP с Go я использую gopls сервер, его нужно установить отдельно. Всю конфигурацию предпочитаю хранить в одном модуле, будь то инициализация LSP для разных ЯП или настройка автодополнений и ИИ к ним (tabnine).
Моя конфигурация LSP (за основу взята стандартная конфигурация из документации lspconfig с небольшими изменениями):
-- ~/.config/nvim/lua/lsp.lua local map = vim.keymap.set local opts = { noremap=true, silent=true } -- удалить ошибки диагностики в левом столбце (SignColumn) vim.diagnostic.config({signs=false}) -- стандартные горячие клавиши для работы с диагностикой map('n', '<leader>e', vim.diagnostic.open_float, opts) map('n', '[d', vim.diagnostic.goto_prev, opts) map('n', ']d', vim.diagnostic.goto_next, opts) map('n', '<leader>q', vim.diagnostic.setloclist, opts) local on_attach = function(client, bufnr) vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc') local bufopts = { noremap=true, silent=true, buffer=bufnr } -- стандартные горячие клавиши для LSP, больше в документации -- https://github.com/neovim/nvim-lspconfig map('n', 'gD', vim.lsp.buf.declaration, bufopts) map('n', 'gd', vim.lsp.buf.definition, bufopts) map('n', 'K', vim.lsp.buf.hover, bufopts) map('n', 'gi', vim.lsp.buf.implementation, bufopts) map('n', '<C-k>', vim.lsp.buf.signature_help, bufopts) map('n', '<leader>wa', vim.lsp.buf.add_workspace_folder, bufopts) map('n', '<leader>wr', vim.lsp.buf.remove_workspace_folder, bufopts) map('n', '<leader>wl', function() print(vim.inspect(vim.lsp.buf.list_workspace_folders())) end, bufopts) map('n', '<leader>D', vim.lsp.buf.type_definition, bufopts) map('n', '<leader>rn', vim.lsp.buf.rename, bufopts) map('n', '<leader>ca', vim.lsp.buf.code_action, bufopts) map('n', 'gr', vim.lsp.buf.references, bufopts) map('n', '<leader>f', vim.lsp.buf.formatting, bufopts) end -- инициализация LSP для различных ЯП local lspconfig = require('lspconfig') local util = require('lspconfig/util') local function config(_config) return vim.tbl_deep_extend('force', { capabilities = require('cmp_nvim_lsp').update_capabilities(vim.lsp.protocol.make_client_capabilities()), }, _config or {}) end -- иницализация gopls LSP для Go -- https://github.com/golang/tools/blob/master/gopls/doc/vim.md#neovim-install lspconfig.gopls.setup(config({ on_attach = on_attach, cmd = { 'gopls', 'serve' }, filetypes = { 'go', 'go.mod' }, root_dir = util.root_pattern('go.work', 'go.mod', '.git'), settings = { gopls = { analyses = { unusedparams = true, shadow = true, }, staticcheck = true, } } }))
Автодополнения
За автодополнения отвечает плагин cmp и если необходимо можно добавить к нему tabnine. Пример настройки:
-- ~/.config/nvim/lua/lsp.lua -- ... map('n', '<leader>f', vim.lsp.buf.formatting, bufopts) end local cmp = require('cmp') local lspkind = require('lspkind') local source_mapping = { buffer = '[Buffer]', nvim_lsp = '[LSP]', nvim_lua = '[Lua]', cmp_tabnine = '[TN]', path = '[Path]', } cmp.setup({ mapping = cmp.mapping.preset.insert({ ['<C-y>'] = cmp.mapping.confirm({ select = true }), ['<C-d>'] = cmp.mapping.scroll_docs(-4), ['<C-u>'] = cmp.mapping.scroll_docs(4), ['<C-Space>'] = cmp.mapping.complete(), }), formatting = { format = function(entry, vim_item) vim_item.kind = lspkind.presets.default[vim_item.kind] local menu = source_mapping[entry.source.name] if entry.source.name == 'cmp_tabnine' then if entry.completion_item.data ~= nil and entry.completion_item.data.detail ~= nil then menu = entry.completion_item.data.detail .. ' ' .. menu end vim_item.kind = '' end vim_item.menu = menu return vim_item end }, sources = cmp.config.sources({ { name = 'cmp_tabnine' }, { name = 'nvim_lsp' }, }, { { name = 'buffer' }, }) }) -- конфиг для ИИ автодополнений local tabnine = require('cmp_tabnine.config') tabnine:setup({ max_lines = 1000, max_num_results = 20, sort = true, run_on_every_keystroke = true, snippet_placeholder = '..', }) -- инициализация LSP для различных ЯП local lspconfig = require('lspconfig') -- ...
Сниппеты
Чтобы автодополнения работали и для сниппетов, в sources объект в setup для cmp нужно прокинуть название движка сниппетов — luasnip, а также описать expand функцию в snippet объекте.
-- ~/.config/nvim/lua/lsp.lua -- ... cmp.setup({ snippet = { expand = function(args) require('luasnip').lsp_expand(args.body) end, }, -- ... sources = cmp.config.sources({ { name = 'cmp_tabnine' }, { name = 'nvim_lsp' }, { name = 'luasnip' }, -- сюда }, { { name = 'buffer' }, }) }) -- ...
Если хочется использовать другой движок для сниппетов, как их подключить есть в документации cmp.
Автокоманды
С помощью автокоманд можно описать команды, которые будут срабатывать на:
-
чтение и запись в файл
-
вход или выход из буфера
-
выход из Neovim
Подробное описание можно посмотреть здесь.
Список автокоманд, которые я использую:
-
Отключить комментирование новой строки (без этой автокоманды, если переходить на новую строку с помощью
oилиOстрока будет закомментирована)
-- ~/.config/nvim/lua/autocommands.lua vim.api.nvim_create_autocmd({'BufEnter'}, { pattern = '*', callback = function() opt.fo:remove('c') opt.fo:remove('r') opt.fo:remove('o') end })
-
Сортировка Go импортов на сохранение файла (взято здесь)
-- ~/.config/nvim/lua/autocommands.lua vim.api.nvim_create_autocmd({'BufWritePre'}, { pattern = '*.go', callback = function() local params = vim.lsp.util.make_range_params(nil, vim.lsp.util._get_offset_encoding()) params.context = { only = {'source.organizeImports'} } local result = vim.lsp.buf_request_sync(0, 'textDocument/codeAction', params, 3000) for _, res in pairs(result or {}) do for _, r in pairs(res.result or {}) do if r.edit then vim.lsp.util.apply_workspace_edit(r.edit, vim.lsp.util._get_offset_encoding()) else vim.lsp.buf.execute_command(r.command) end end end end, })
-
Форматирование Go файлов на запись:
-- ~/.config/nvim/lua/autocommands.lua vim.api.nvim_create_autocmd({'BufWritePre'}, { pattern = '*.go', callback = function() vim.lsp.buf.formatting_sync(nil, 3000) end })
-
Удаление плавающих пробелов на запись:
-- ~/.config/nvim/lua/autocommands.lua local TrimWhiteSpaceGrp = vim.api.nvim_create_augroup ('TrimWhiteSpaceGrp', {}) vim.api.nvim_create_autocmd('BufWritePre', { group = TrimWhiteSpaceGrp, pattern = '*', command = '%s/\\s\\+$//e', })
-
Подсветка скопированных строк
-- ~/.config/nvim/lua/autocommands.lua local YankHighlightGrp = vim.api.nvim_create_augroup('YankHighlightGrp', {}) vim.api.nvim_create_autocmd('TextYankPost', { group = YankHighlightGrp, pattern = '*', callback = function() vim.highlight.on_yank({ higroup = 'IncSearch', timeout = 40, }) end, })
Общие горячие клавиши
Горячие клавиши для плагинов лучше описывать в модулях соответствующих плагинов. В ~/.config/nvim/lua/keymaps.lua я обычно описываю горячие клавиши, не зависящие от внешних зависимостей.
-- ~/.config/nvim/lua/keymaps.lua local map = vim.api.nvim_set_keymap local opts = {noremap = true, silent = true} -- переход в Ex мод map('n', '<leader>pv', ':Ex<CR>', opts) map('n', 'Q', '<nop>', opts) -- анбинд Q -- упрощенная индентация map('v', '<', '<gv', opts) map('v', '>', '>gv', opts) -- отключение стрелочек (только hjkl) map('', '<up>', '', opts) map('', '<down>', '', opts) map('', '<left>', '', opts) map('', '<right>', '', opts)
Источники
Мои dotfiles
Дотфайлы
Статьи
ссылка на оригинал статьи https://habr.com/ru/post/678298/
Добавить комментарий