DEV Community

Heiker
Heiker

Posted on Edited on Originally published at vonheikemen.github.io

Se puede usar el cliente LSP de neovim sin plugins?

Sí se puede. La complejidad de la configuraciÃģn dependerÃĄ del flujo de trabajo que queremos utilizar. Pero si lua les resulta fÃĄcil de leer podrÃĄn entender la "estructura" necesaria para obtener una configuraciÃģn decente.

La base

Resulta que sÃģlo necesitamos conocer dos funciones. Una para inicializar el servidor LSP. Una para notificar cada cambio en el texto un archivo.

  • vim.lsp.start_client(): Crea una instancia de un "cliente" que es la que se encarga de comunicarse con el servidor LSP.

  • vim.lsp.buf_attach_client(): Envía notificaciones al servidor LSP con cada cambio en el texto.

Este es un ejemplo simple e ineficiente usando un servidor LSP para typescript.

local launch_tsserver = function()
  local config = {
    cmd = {'typescript-language-server', '--stdio'},
    name = 'tsserver',
    root_dir = vim.fn.getcwd(),
  }

  local client_id = vim.lsp.start_client(config)

  if client_id then
    vim.lsp.buf_attach_client(0, client_id)
  end
end

vim.api.nvim_create_user_command(
  'LaunchTsserver',
  launch_tsserver,
  {desc = 'Inicializar tsserver'}
)
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/se puede usar el cliente lsp de neovim sin plugins ha - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/se puede usar el cliente lsp de neovim sin plugins ha - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Con esto creamos el comando LaunchTsserver, al ejecutarlo podremos usar el servidor typescript-language-server y obtener diagnÃģsticos en el archivo actual.

ÂŋPor quÃĐ es ineficiente? Bueno, porque sÃģlo funciona con un solo archivo. Y cada vez que lo ejecutamos estamos creando un nuevo proceso de typescript-language-server. Lo ideal sería poder compartir el mismo servidor en todo el proyecto.

Uso en proyectos

ÂŋQuÃĐ nos falta para poder utilizar el servidor LSP de manera eficiente? Necesitamos crear un "autocomando". Debemos indicarle a neovim que queremos vincular una extensiÃģn o tipo de archivo con un servidor LSP.

local filetypes = {
  'typescript',
  'javascript',
  'typescriptreact',
  'javascriptreact',
  'typescript.tsx',
  'javascript.jsx'
}

local buf_attach = function()
  vim.lsp.buf_attach_client(0, client_id)
end

autocmd_id = vim.api.nvim_create_autocmd('FileType', {
  desc = string.format('Vincular servidor: %s', client_name),
  pattern = filetypes,
  callback = buf_attach
})
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/se puede usar el cliente lsp de neovim sin plugins ha - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/se puede usar el cliente lsp de neovim sin plugins ha - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Bien, ahora nvim_create_autocmd nos devolverÃĄ un id que podremos usar para eliminar el autocomando cuando sea necesario.

Para eliminar el autocomando necesitaremos la funciÃģn nvim_del_autocmd.

vim.api.nvim_del_autocmd(autocmd_id)
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/se puede usar el cliente lsp de neovim sin plugins ha - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/se puede usar el cliente lsp de neovim sin plugins ha - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

ÂŋPero en quÃĐ momento debemos crear este autocomando? Cuando el servidor termine su proceso de inicializaciÃģn. Podemos usar la funciÃģn on_init para crearlo y despuÃĐs usamos on_exit para borrarlo.

Si aplicamos todo este conocimiento a nuestro ejemplo, la funciÃģn launch_tsserver quedaría de la siguiente manera.

local launch_tsserver = function()
  local autocmd
  local filetypes = {
    'typescript',
    'javascript',
    'typescriptreact',
    'javascriptreact',
    'typescript.tsx',
    'javascript.jsx'
  }

  local config = {
    cmd = {'typescript-language-server', '--stdio'},
    name = 'tsserver',
    root_dir = vim.fn.getcwd(),
  }

  config.on_init = function(client, results)
    local buf_attach = function()
      vim.lsp.buf_attach_client(0, client.id)
    end

    autocmd = vim.api.nvim_create_autocmd('FileType', {
      desc = string.format('Vincular servidor: %s', client.name),
      pattern = filetypes,
      callback = buf_attach
    })

    if vim.v.vim_did_enter == 1 and
      vim.tbl_contains(filetypes, vim.bo.filetype)
    then
      buf_attach()
    end
  end

  config.on_exit = vim.schedule_wrap(function(code, signal, client_id)
    vim.api.nvim_del_autocmd(autocmd)
  end)

  vim.lsp.start_client(config)
end
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/se puede usar el cliente lsp de neovim sin plugins ha - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/se puede usar el cliente lsp de neovim sin plugins ha - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Configuraciones de servidor

Nuestro ejemplo aÚn no estÃĄ completo. La mayoría de los servidores LSP tienen una serie de opciones Únicas, para habilitar funcionalidades o modificar algÚn comportamiento. Justo ahora no le estamos enviando esa informaciÃģn al servidor.

DespuÃĐs de que el servidor estÃĐ listo podemos enviarle una "notificaciÃģn" con los datos que queremos. Entonces, en la funciÃģn on_init agregamos esto.

if results.offsetEncoding then
  client.offset_encoding = results.offsetEncoding
end

if client.config.settings then
  client.notify('workspace/didChangeConfiguration', {
    settings = client.config.settings
  })
end
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/se puede usar el cliente lsp de neovim sin plugins ha - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/se puede usar el cliente lsp de neovim sin plugins ha - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

La documentaciÃģn de neovim sugiere que debemos configurar la codificaciÃģn del cliente antes de enviar cualquier peticiÃģn o notificaciÃģn. Entonces si el servidor nos devuelve esta informaciÃģn en results lo usamos en el cliente.

Si la instancia del cliente tiene la propiedad settings nos aseguramos de enviarla al servidor. Vale la pena mencionar que client.config es una copia del argumento que le pasamos a la funciÃģn vim.lsp.start_client().

ÂŋCuÃĄndo definimos los atajos?

No podemos olvidarnos de los atajos. ÂŋCÃģmo debemos proceder? La documentaciÃģn nos sugiere que usemos la funciÃģn on_attach para definir los atajos y cualquier otro comando. Pero yo tengo otra sugerencia, yo digo que usemos un "evento de usuario" así podemos definir los atajos en cualquier lugar de nuestra configuraciÃģn.

config.on_attach = function(client, bufnr)
  vim.api.nvim_exec_autocmds('User', {pattern = 'LspAttached'})
end
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/se puede usar el cliente lsp de neovim sin plugins ha - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/se puede usar el cliente lsp de neovim sin plugins ha - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Ahora podemos definir nuestros atajos en otro lugar.

vim.api.nvim_create_autocmd('User', {
  pattern = 'LspAttached',
  desc = 'Acciones LSP',
  callback = function()
    local bufmap = function(mode, lhs, rhs)
      local opts = {buffer = true}
      vim.keymap.set(mode, lhs, rhs, opts)
    end

    -- Configura sugerencias del servidor
    -- se activan presionando Ctrl-x + Ctrl-o en modo de inserciÃģn
    vim.bo.omnifunc = 'v:lua.vim.lsp.omnifunc'

    -- Comandos y funciones que trabajan con "tags" podrÃĄn
    -- usar el servidor LSP como fuente
    vim.bo.tagfunc = 'v:lua.vim.lsp.tagfunc'

    -- Muestra informaciÃģn sobre símbolo debajo del cursor
    bufmap('n', 'K', '<cmd>lua vim.lsp.buf.hover()<cr>')

    -- Saltar a definiciÃģn
    bufmap('n', 'gd', '<cmd>lua vim.lsp.buf.definition()<cr>')

    -- Saltar a declaraciÃģn
    bufmap('n', 'gD', '<cmd>lua vim.lsp.buf.declaration()<cr>')

    -- Mostrar implementaciones
    bufmap('n', 'gi', '<cmd>lua vim.lsp.buf.implementation()<cr>')

    -- Saltar a definiciÃģn de tipo
    bufmap('n', 'go', '<cmd>lua vim.lsp.buf.type_definition()<cr>')

    -- Listar referencias
    bufmap('n', 'gr', '<cmd>lua vim.lsp.buf.references()<cr>')

    -- Mostrar argumentos de funciÃģn
    bufmap('n', '<C-k>', '<cmd>lua vim.lsp.buf.signature_help()<cr>')

    -- Renombrar símbolo
    bufmap('n', '<F2>', '<cmd>lua vim.lsp.buf.rename()<cr>')

    -- Listar "code actions" disponibles en la posiciÃģn del cursor
    bufmap('n', '<F4>', '<cmd>lua vim.lsp.buf.code_action()<cr>')
    bufmap('x', '<F4>', '<cmd>lua vim.lsp.buf.range_code_action()<cr>')

    -- Mostrar diagnÃģsticos de la línea actual
    bufmap('n', 'gl', '<cmd>lua vim.diagnostic.open_float()<cr>')

    -- Saltar al diagnÃģstico anterior
    bufmap('n', '[d', '<cmd>lua vim.diagnostic.goto_prev()<cr>')

    -- Saltar al siguiente diagnÃģstico
    bufmap('n', ']d', '<cmd>lua vim.diagnostic.goto_next()<cr>')
  end
})
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/se puede usar el cliente lsp de neovim sin plugins ha - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/se puede usar el cliente lsp de neovim sin plugins ha - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

CÃģdigo completo

Si aplicamos todos estos cambios a nuestro ejemplo obtendremos esto.

local launch_tsserver = function()
  local autocmd
  local filetypes = {
    'typescript',
    'javascript',
    'typescriptreact',
    'javascriptreact',
    'typescript.tsx',
    'javascript.jsx'
  }

  local config = {
    cmd = {'typescript-language-server', '--stdio'},
    name = 'tsserver',
    root_dir = vim.fn.getcwd(),
    capabilities = vim.lsp.protocol.make_client_capabilities(),
  }

  config.on_attach = function(client, bufnr)
    vim.api.nvim_exec_autocmds('User', {pattern = 'LspAttached'})
  end

  config.on_init = function(client, results)
    if results.offsetEncoding then
      client.offset_encoding = results.offsetEncoding
    end

    if client.config.settings then
      client.notify('workspace/didChangeConfiguration', {
        settings = client.config.settings
      })
    end

    local buf_attach = function()
      vim.lsp.buf_attach_client(0, client.id)
    end

    autocmd = vim.api.nvim_create_autocmd('FileType', {
      desc = string.format('Vincular servidor: %s', client.name),
      pattern = filetypes,
      callback = buf_attach
    })

    if vim.v.vim_did_enter == 1 and
      vim.tbl_contains(filetypes, vim.bo.filetype)
    then
      buf_attach()
    end
  end

  config.on_exit = vim.schedule_wrap(function(code, signal, client_id)
    vim.api.nvim_del_autocmd(autocmd)
  end)

  vim.lsp.start_client(config)
end

vim.api.nvim_create_user_command(
  'LaunchTsserver',
  launch_tsserver,
  {desc = 'Inicializar tsserver'}
)
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/se puede usar el cliente lsp de neovim sin plugins ha - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/se puede usar el cliente lsp de neovim sin plugins ha - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

ÂŋPodemos mejorarlo?

Claro. Casi todo el cÃģdigo puede moverse a una funciÃģn auxiliar, con eso podremos eliminar todo el ruido.

SÃģlo imaginen algo como esto.

local launch_tsserver = function()
  local config = make_config({
    cmd = {'typescript-language-server', '--stdio'},
    name = 'tsserver',
    filetypes = {
      'typescript',
      'javascript',
      'typescriptreact',
      'javascriptreact',
      'typescript.tsx',
      'javascript.jsx'
    }
  })

  vim.lsp.start_client(config)
end

vim.api.nvim_create_user_command(
  'LaunchTsserver',
  launch_tsserver,
  {desc = 'Inicializar tsserver'}
)
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/se puede usar el cliente lsp de neovim sin plugins ha - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/se puede usar el cliente lsp de neovim sin plugins ha - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

ÂŋY quÃĐ tiene make_config? Bueno... se los dejo de tarea. TendrÃĄn que implementarla ustedes mismos. Ya les mostrÃĐ todo el cÃģdigo necesario confío en que pueden hacerlo solos.

Si de verdad quieren saber quÃĐ haría yo pueden encontrar la respuesta en este repositorio: VonHeikemen/nvim-lsp-sans-plugins

ConclusiÃģn

Aprendimos suficiente sobre el cliente LSP que viene incluido en neovim para crear una pequeÃąa configuraciÃģn. Sabemos cÃģmo reutilizar una instancia de servidor LSP en mÚltiples archivos. Y en el proceso aprendimos un poco sobre los autocomandos. Ya podemos decir que podemos manejar el cliente LSP de neovim.

Fuentes


Gracias por su tiempo. Si este artículo les pareciÃģ Útil y quieren apoyar mis esfuerzos para crear mÃĄs contenido pueden dejar una propina en ko-fi.com/vonheikemen.

buy me a coffee

Top comments (0)