LSP, Autocomplete e Machine Learning - Neovim com Lua

Instalamos LSP do Neovim, assinatura de função, snippets e entre outros e deixamos muito mais confortável!


LSP, Autocomplete e Machine Learning - Neovim com Lua

Hoje vamos finalizar nossa série aqui no blog, mas ainda teremos um vídeo(somente para instalarmos alguns plugins adicionais e revisar conceitos) para completar essa série e o vídeo postarei aqui e no primeiro artigo dessa série assim que ficar pronto.

Se você não viu os capítulos anteriores, segue os links:

E nesse artigo vamos instalar e configurar:

  • nvim-lsp - Um servidor de linguagem(LSP, serve para completar palavras reservadas da linguagem que estiver escrevendo) criado pelo próprio time do Neovim;
  • nvim-cmp - Um autocomplete para exibir as opções do servidor de linguagem e entre outros;
  • cmp-nvim-lsp - Autocomplete específico para LSP;
  • cmp-buffer - Para obter os termos do buffer;
  • cmp-path - Para obtermos os caminhos do sistema(Ex.: /home/user/arquivo) quando começarmos a digitar;
  • cmp-cmdline - Para linha de comando;
  • LuaSnip - Para snnipets;
  • cmp_luasnip - Fonte dos snnipets para o LuaSnip;
  • friendly-snippets - Completar os snippets com <Tab>;
  • lsp_signature.nvim - Para obtermos a assinatura da função que utilizarmos;
  • cmp-tabnine - Machine Learning para um autocomplete mais avançado;
  • lspkind-nvim - Quando o combo do autocomplete for aberto exibir ícones e informações amigáveis .


Instalando o LSP e o Autocomplete

Já de primeira vamos adicionar todos os plugins e depois vamos criar os arquivos de configuração porque todos ficarão aninhados.

Adicione essas linhas ao seu ~/.config/nvim/lua/plugins/plugins.lua:

use 'neovim/nvim-lspconfig'
use 'hrsh7th/nvim-cmp'
use 'hrsh7th/cmp-nvim-lsp'
use 'hrsh7th/cmp-buffer'
use 'hrsh7th/cmp-path'
use 'hrsh7th/cmp-cmdline'
use 'saadparwaiz1/cmp_luasnip'
use 'L3MON4D3/LuaSnip'
use 'rafamadriz/friendly-snippets'
use 'ray-x/lsp_signature.nvim'
use {'tzachar/cmp-tabnine', run='./install.sh', requires = 'hrsh7th/nvim-cmp'}
use 'onsails/lspkind-nvim'

Perceba que o tabnine rodará um script sh assim que for adicionado e possui o nvim-cmp como dependência.

E em seguida rode: :PackerInstall .


Configurando para o Tabnine

Agora vamos criar outro arquivo ainda em : ~/.config/nvim/lua/plugins/tabnine.lua e adicionar o código para Machine Learning:

local has_any_words_before = function()
  if vim.api.nvim_buf_get_option(0, "buftype") == "prompt" then
    return false
  end
  local line, col = unpack(vim.api.nvim_win_get_cursor(0))
  return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match("%s") == nil
end

require'lspconfig'.clangd.setup{}
require "lsp_signature".setup()
vim.o.completeopt = 'menuone,noselect'

local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities = require('cmp_nvim_lsp').update_capabilities(capabilities)

local cmp = require'cmp'
local luasnip = require("luasnip")

cmp.setup({
  mapping = {

    ['<C-n>'] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Insert }),
    ['<C-p>'] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Insert }),
    ['<Down>'] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Select }),
    ['<Up>'] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Select }),
    ['<C-d>'] = cmp.mapping.scroll_docs(-4),
    ['<C-f>'] = cmp.mapping.scroll_docs(4),
    ['<C-Space>'] = cmp.mapping.complete(),
    ['<C-e>'] = cmp.mapping.close(),
    ['<CR>'] = cmp.mapping.confirm({
      behavior = cmp.ConfirmBehavior.Replace,
      select = true,
    }),

    ['<Tab>'] = function(fallback)
      if cmp.visible() then
        cmp.select_next_item()
      elseif luasnip.expand_or_jumpable() then
        vim.fn.feedkeys(vim.api.nvim_replace_termcodes('<Plug>luasnip-expand-or-jump', true, true, true), '')
      else
        fallback()
      end
    end,
    ['<S-Tab>'] = function(fallback)
      if cmp.visible() then
        cmp.select_prev_item()
      elseif luasnip.jumpable(-1) then
        vim.fn.feedkeys(vim.api.nvim_replace_termcodes('<Plug>luasnip-jump-prev', true, true, true), '')
      else
        fallback()
      end
    end,

  },

  snippet = {
    expand = function(args)
      require('luasnip').lsp_expand(args.body)
    end,
  },
  sources = {
    { name = 'cmp_tabnine' },
    { name = 'luasnip' },
    { name = 'path' },
  },
})
local tabnine = require('cmp_tabnine.config')
tabnine:setup({
  max_lines = 1000;
  max_num_results = 20;
  sort = true;
  run_on_every_keystroke = true;
  snippet_placeholder = '..';
})

require('lspkind').init({
  with_text = true,
  preset = 'codicons',
  symbol_map = {
    Text = "",
    Method = "",
    Function = "",
    Constructor = "",
    Field = "ﰠ",
    Variable = "",
    Class = "ﴯ",
    Interface = "",
    Module = "",
    Property = "ﰠ",
    Unit = "塞",
    Value = "",
    Enum = "",
    Keyword = "",
    Snippet = "",
    Color = "",
    File = "",
    Reference = "",
    Folder = "",
    EnumMember = "",
    Constant = "",
    Struct = "פּ",
    Event = "",
    Operator = "",
    TypeParameter = ""
  },
})

require("luasnip/loaders/from_vscode").load()

local lspkind = require('lspkind')

local source_mapping = {
  buffer = "◉ Buffer",
  nvim_lsp = "👐 LSP",
  nvim_lua = "🌙 Lua",
  cmp_tabnine = "💡 Tabnine",
  path = "🚧 Path",
  luasnip = "🌜 LuaSnip"
}

require'cmp'.setup {
  sources = {
    { name = 'cmp_tabnine' },
    { name = 'luasnip' },
    { name = 'path' }
  },
  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
  },
}

Ainda NÃO adicione ao seu init.lua, pois vamos criar uma condição.


Configurações para o LSP

Vamos criar outro arquivo(~/.config/nvim/lua/plugins/lsp.lua) com o código parecido(você pode achar que haverá codigo duplicado, mas estamos usando todas as variáveis locais e o arquivo só será carregado de acordo com o arquivo posterior que vamos criar), mas para o LSP:

local has_any_words_before = function()
  if vim.api.nvim_buf_get_option(0, "buftype") == "prompt" then
    return false
  end
  local line, col = unpack(vim.api.nvim_win_get_cursor(0))
  return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match("%s") == nil
end

require'lspconfig'.clangd.setup{}
require "lsp_signature".setup()
vim.o.completeopt = 'menuone,noselect'

local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities = require('cmp_nvim_lsp').update_capabilities(capabilities)

local cmp = require'cmp'
local luasnip = require("luasnip")

local lspkind = require('lspkind')
local source_mapping = {
  buffer = "◉ Buffer",
  nvim_lsp = "👐 LSP",
  nvim_lua = "🌙 Lua",
  cmp_tabnine = "💡 Tabnine",
  path = "🚧 Path",
  luasnip = "🌜 LuaSnip"
}

cmp.setup({
  sources = {
    { name = 'nvim_lsp' },
    { name = 'luasnip' },
    { name = 'buffer' },
    { name = 'path' },
    { name = 'nvim_lua' },
  },

  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
  },

  snippet = {
    expand = function(args)
      require('luasnip').lsp_expand(args.body)
    end,
  },
  mapping = {

    ['<C-n>'] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Insert }),
    ['<C-p>'] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Insert }),
    ['<Down>'] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Select }),
    ['<Up>'] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Select }),
    ['<C-d>'] = cmp.mapping.scroll_docs(-4),
    ['<C-f>'] = cmp.mapping.scroll_docs(4),
    ['<C-Space>'] = cmp.mapping.complete(),
    ['<C-e>'] = cmp.mapping.close(),
    ['<CR>'] = cmp.mapping.confirm({
      behavior = cmp.ConfirmBehavior.Replace,
      select = true,
    }),

    ['<Tab>'] = function(fallback)
      if cmp.visible() then
        cmp.select_next_item()
      elseif luasnip.expand_or_jumpable() then
        vim.fn.feedkeys(vim.api.nvim_replace_termcodes('<Plug>luasnip-expand-or-jump', true, true, true), '')
      else
        fallback()
      end
    end,
    ['<S-Tab>'] = function(fallback)
      if cmp.visible() then
        cmp.select_prev_item()
      elseif luasnip.jumpable(-1) then
        vim.fn.feedkeys(vim.api.nvim_replace_termcodes('<Plug>luasnip-jump-prev', true, true, true), '')
      else
        fallback()
      end
    end,
  },
})

require("luasnip/loaders/from_vscode").load()

Configurando os arquivos dinamicamente

Existe alguns detalhes para fazer todos funcionarem normalmente. O tabnine é interessante rodá-lo quando não há LSP disponível, geralmente eu incluo nos arquivos Markdown porque fica mais fácil, já quando estou escrevendo código, ele pode atrapalhar seu LSP.

Então, para separarmos vamos fazer o seguinte, vamos criar um arquivo dentro de lua/plugins de nome complete.lua e inserir o seguinte conteúdo:

vim ~/.config/nvim/lua/plugins/complete.lua

if extension == "md" then
  require("plugins.tabnine")
else

  require("plugins.lsp")
end

E para carregar esse novo arquivo, importe no seu init.lua:

require("plugins.complete")

Agora é só testar!

No vídeo dessa série vou explicar os trechos de cada código e adicionar mais coisas!

Veja abaixo alguns vídeos/gif/imagem dos plugins que instalamos:

snippets

Snippets


Assinatura de funções

lsp_signature

Autocomplete


LSP Kind

lspkind


Outros artigos da série

  1. Como Customizar do Zero
  2. Do init.vim para o init.lua
  3. Instalando Plugins
  4. Personalizando a Aparência
  5. LSP, Autocomplete e Machine Learning

No próximo será o vídeo!!!


Valeu e até mais!


neovim tabnine


Compartilhe


Nosso canal no Youtube

Inscreva-se


Marcos Oliveira

Marcos Oliveira

Desenvolvedor de software
https://github.com/terroo


Crie Aplicativos Gráficos para Linux e Windows com C++

Aprenda C++ Moderno e crie Games, Programas CLI, GUI e TUI de forma fácil.

Saiba Mais

Receba as novidades no seu e-mail!

Após cadastro e confirmação do e-mail, enviaremos semanalmente resumos e também sempre que houver novidades por aqui para que você mantenha-se atualizado!