Lua конфигурация Neovim для разработки на Go

от автора

Предисловие

В процессе знакомства с 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 и плагинов

Подсветка синтаксиса и навигация

  1. 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 } }
  1. 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *