Shopify / ruby-lsp

An opinionated language server for Ruby
https://shopify.github.io/ruby-lsp/
MIT License
1.54k stars 146 forks source link

Support for Neovim #188

Closed pBorak closed 1 year ago

pBorak commented 2 years ago

Let me start by saying I am so excited that ruby tooling has gotten some love lately. Are there a plans to add support for other editors (most notably) Neovim? Neovim since version 0.5 natively supports Language Server Protocol and there is a significant subset of already supported LSP.

vinistock commented 2 years ago

Hey! Based on my understanding, we'd need to add a file similar to this one for the sorbet LSP, correct?

In terms of basic configuration, I believe all that'd be needed is

Here is where we send the initializationOptions in VS Code. It's just an array of strings like

enabledFeatures: ["documentSymbols", "foldingRanges", ...]

And here are all of the feature names.

I'm not super familiar with Neovim. Could we test this out and make sure it works before contributing to nvim-lspconfigs?

alexwu commented 2 years ago

I just wanted to hop in and drop my neovim config that I've been using for a little while:

local lspconfig = require("lspconfig")
local configs = require("lspconfig.configs")
local util = require("lspconfig.util")

if not configs.ruby_lsp then
    local enabled_features = {
        "documentHighlights",
        "documentSymbols",
        "foldingRanges",
        "selectionRanges",
        -- "semanticHighlighting",
        "formatting",
        "codeActions",
    }

    configs.ruby_lsp = {
        default_config = {
            cmd = { "bundle", "exec", "ruby-lsp" },
            filetypes = { "ruby" },
            root_dir = util.root_pattern("Gemfile", ".git"),
            init_options = {
                enabledFeatures = enabled_features,
            },
            settings = {},
        },
        commands = {
            FormatRuby = {
                function()
                    vim.lsp.buf.format({
                        name = "ruby_lsp",
                        async = true,
                    })
                end,
                description = "Format using ruby-lsp",
            },
        },
    }
end

lspconfig.ruby_lsp.setup({ on_attach = on_attach, capabilities = capabilities })

I hope this helps!

pBorak commented 2 years ago

@alexwu Thanks! Isn't bundle exec required in the cmd tho?

vinistock commented 2 years ago

If you use it without bundler and your app has RuboCop extensions (e.g.: rubocop-performance), it will fail when trying to require them.

We are going to explore allowing the Ruby LSP to be executed without bundler, but then it'll probably fall back to formatting files using SyntaxTree and disable all RuboCop functionality.

alexwu commented 2 years ago

@alexwu Thanks! Isn't bundle exec required in the cmd tho?

Nice catch -- I've updated the snippet it to use bundler.

alexventuraio commented 2 years ago

To try this, I just need to install the ruby-lsp gem and where should I put @alexwu config?

wassimk commented 2 years ago

@alexventuraio, after you install ruby-lsp via your Gemfile and the nvim-lspconfig plugin, you can put @alexwu instruction anywhere in your Vim configuration. You'll probably want to create some keymaps for jumping around. Look at the nvim-lspconfig README for a good starting setup.

If your vim configuration is not in Lua, then you insert Lua code with:

lua <<EOF
print('hello from lua')
EOF
vinistock commented 2 years ago

While our team's focus is on supporting VS Code, we'd gladly add an EDITOR_SETUP.md file linked from the README with community driven instructions on how to get the Ruby LSP working on other editors.

Would that be helpful? We could get started with neovim.

wassimk commented 2 years ago

@vinistock yes, I think that would be helpful. I have a branch adding ruby-lsp to the nvim-lspconfig project. Once that goes through I'll be happy to write some Neovim instructions.

alexventuraio commented 2 years ago

@wassimk thanks for sharing your solution, I'm looking forward to go through your PR and read your instructions to get it done.

cj commented 2 years ago

@alexwu thank you for your config, it worked great. I did notice one issue, diagnostics errors do not show.

wassimk commented 2 years ago

@cj I believe diagnostics stopped working with the Neovim LSP client with this change https://github.com/Shopify/ruby-lsp/pull/242.

vinistock commented 2 years ago

Does the Neovim LSP support the specification version 3.17? It should work if it does.

alexwu commented 2 years ago

@cj @wassimk Darn, I've been a bit behind on ruby-lsp versions of late, so I didn't know about this.

I'll see if there's any info on neovim supporting that spec when I get the chance.

jameswritescode commented 1 year ago

neovim does not support textDocument/diagnostic with no official plans to support it

from matrix:

   newton | are there any plans to support textDocument/diagnostic to request diagnostics from LSP?
gpanders_ | no current plans, but that does not mean a PR implementing that support would be rejected

here's a snippet nvim lsp users can incorporate into their configs to request textDocument/diagnostic regularly:

on_attach = function(client, bufnr)
  vim.api.nvim_create_autocmd({ 'BufEnter', 'BufWritePre', 'CursorHold' }, {
    buffer = bufnr,

    callback = function()
      local params = vim.lsp.util.make_text_document_params(bufnr)

      client.request(
        'textDocument/diagnostic',
        { textDocument = params },
        function(err, result)
          if err then return end
          if not result then return end

          vim.lsp.diagnostic.on_publish_diagnostics(
            nil,
            vim.tbl_extend('keep', params, { diagnostics = result.items }),
            { client_id = client.id }
          )
        end
      )
    end,
  })
end
technicalpickles commented 1 year ago

It looks like lspconfig now ships with support directly: https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#ruby_ls

The only thing you may need to change the cmd to use bundler, ie:

local lspconfig = require("lspconfig")

lspconfig.ruby_ls.setup({
    cmd = { "bundle", "exec", "ruby-lsp" }
})

edit added a note about what you may need to change

technicalpickles commented 1 year ago

here's a snippet nvim lsp users can incorporate into their configs to request textDocument/diagnostic regularly

I have tried this snippet, and one problem I'm seeing is that you don't get diagnostics when I first load a file. I have to make a change and save. I tried addding like BufRead or BufReadPre, but had same results (I'm not super familiar with autocmd stuff)

technicalpickles commented 1 year ago

One downside I'm seeing for using the lsp is that you'll see an error about connecting to the LSP when the Gemfile doesn't have it. I'm not sure if there's a way to have a callback to determine that 🤔

mihyaeru21 commented 1 year ago

I have tried this snippet, and one problem I'm seeing is that you don't get diagnostics when I first load a file. I have to make a change and save. I tried addding like BufRead or BufReadPre, but had same results (I'm not super familiar with autocmd stuff)

This worked fine for me.

on_attach = function(client, buffer)
  local callback = function()
    local params = vim.lsp.util.make_text_document_params(buffer)

    client.request(
      'textDocument/diagnostic',
      { textDocument = params },
      function(err, result)
        if err then return end

        vim.lsp.diagnostic.on_publish_diagnostics(
          nil,
          vim.tbl_extend('keep', params, { diagnostics = result.items }),
          { client_id = client.id }
        )
      end
    )
  end

  callback() -- call on attach

  vim.api.nvim_create_autocmd({ 'BufEnter', 'BufWritePre', 'BufReadPost', 'InsertLeave', 'TextChanged' }, {
    buffer = buffer,
    callback = callback,
  })
end
technicalpickles commented 1 year ago

One downside I'm seeing for using the lsp is that you'll see an error about connecting to the LSP when the Gemfile doesn't have it. I'm not sure if there's a way to have a callback to determine that

I found a way to do this 🎉 I was looking at adding on_new_config, but in looking for options I came across https://github.com/neovim/nvim-lspconfig/issues/1886 and https://github.com/mihyaeru21/nvim-lspconfig-bundler . Confirmed the plugin runs through bundler 🎉

arashm commented 1 year ago

coc.nvim also kinda supports it https://github.com/neoclide/coc.nvim/wiki/Language-servers#using-shopifyruby-lsp

vinistock commented 1 year ago

Folks, I added an editors file to aggregate configurations for the Ruby LSP in editors other than VS Code. If somebody wants to contribute instructions for NeoVim, we can put it there and close this issue.

We can also include multiple alternatives if there's not a single way of doing it.

smarquez1 commented 1 year ago

Link to related neovim and nvim-lspconfig issues:

vinistock commented 1 year ago

Another thing that might help: since #562, we switched features from being opt-in to being opt-out so that configuration is easier for editors (everything is enabled by default if no config is passed).

b-sep commented 1 year ago

any help?

lsp_config.lua

local status_ok, lspconfig = pcall(require, 'lspconfig')
if not status_ok then
  return
end

local servers = require("junior.lsp.servers")

for _, server in pairs(servers) do
  local opts = {}
  local handler = require("junior.lsp.handlers")

  opts = {
    on_attach = handler.on_attach,
    capabilities = handler.capabilities,
  }

  if server == 'lua_ls' then
    opts = vim.tbl_deep_extend("force", {
      settings = {
        Lua = {
          diagnostics = {
            globals = { "vim" },
          },
        },
      },
    }, opts)
  end

  lspconfig[server].setup(opts)
end

servers.lua

local servers = {
  'lua_ls',
  'tsserver',
  'ruby_ls'
}

return servers

When i open a buffer with ruby file, i can see the the ruby_ls has attached to the buffer but i dont get any completion or diagnostic. I already tried the suggestions on this issue and nothing happens. Im have the gem installed btw.

image

on the LspLog i have this [ERROR] but i dont know what this means

image

cefigueiredo commented 1 year ago

@b-sep We can not see the handler where you customize the on-attach. But probably it's missing the workaround to make neovim send diagnostic requests to ruby-lsp as suggested on a prior comment, and detailed here neovim/nvim-lspconfig#2498

b-sep commented 1 year ago

@b-sep We can not see the handler where you customize the on-attach. But probably it's missing the workaround to make neovim send diagnostic requests to ruby-lsp as suggested on a prior comment, and detailed here neovim/nvim-lspconfig#2498

my handlers file is just a bunch of mappings and visual stuff, you can see here

i'll try the workaround that u link, ty :)

technicalpickles commented 1 year ago

I'm not sure if this is neovim specific, or a broader ruby-lsp question: if you use sorbet, is there any guidance/recommendations for using the sorbet LSP and ruby-lsp together? There is definitely some overlap as noted in https://github.com/Shopify/ruby-lsp/issues/206

I'm still learning and understanding nvim + lsp, but I get the impression only one ruby LSP ends up being attached.

vinistock commented 1 year ago

The default behaviour for language servers is to allow for more than one and merge responses. If both the Ruby LSP and Sorbet implement the same request, you'd just get the results duplicated in the editor.

We use both Sorbet and the Ruby LSP on our projects (even on the Ruby LSP project itself, we use both). This works out of the box on VS Code, but I have no idea how NeoVim handles it.

jameswritescode commented 1 year ago

The default behaviour for language servers is to allow for more than one and merge responses. If both the Ruby LSP and Sorbet implement the same request, you'd just get the results duplicated in the editor.

This is also my experience using both together.

I'm still learning and understanding nvim + lsp, but I get the impression only one ruby LSP ends up being attached.

@technicalpickles Looking at your committed configuration here sorbet is autostart = false so unless you are manually starting it later I assume it wouldn't attach.

technicalpickles commented 1 year ago

I stand corrected, thanks for the info! I'll need to look more closely how things behave when both ruby-lsp and sorbet are enabled to see if there is actually any duplicates.

Looking at your committed configuration here sorbet is autostart = false so unless you are manually starting it later I assume it wouldn't attach.

Thanks for checking that out. I was in a interim stage while debugging some other LSP related things 😅

technicalpickles commented 1 year ago

I posted this over on @cefigueiredo's https://github.com/neovim/nvim-lspconfig/pull/2498 , but figured would share here too:

Would it be reasonable to just added the recommended workaround to lua/lspconfig/server_configurations/ruby_ls.lua rather than documenting the workaround? That way, users get a better out of box experience until there is support in neovim itself.

I can think of a few ways to detect the older versions, but they are kinda dirty. ruby-lsp doesn't have a --version flag unfortunately. It would be in someone's Gemfile.lock if they have it for their project, but that isn't always how it's installed.

Maybe an option to work for new vs old ruby-lsp?

I'm just trying to work out what the easiest way to enable vim folks to use ruby-lsp without needing much extra configuration. I think it's moving the workaround into nvim-lspconfig, or maybe another package?

Is there anything that can be implemented within ruby-lsp?

metalelf0 commented 1 year ago

I'm trying to set ruby-lsp up in my neovim config. The gem is installed correctly and it seems to start fine. When I enter any ruby file, though, I get this error in the LspLog:

LSP[ruby_ls]: Error INVALID_SERVER_MESSAGE: {
  jsonrpc = "2.0"
}

I don't think I have anything fancy set up, just the usual nvim-lspconfig stuff + mason and diagnostics, but from the error it looks like neovim is not even "establishing a communication" with the LSP, right?

I tried searching online but I have literally no clue about where to start to address this issue.

Any clue about what I might be missing / doing wrong? My dot files are here:

https://github.com/metalelf0/dot-files/tree/master/.config/nvim

Thanks in advance!

atishdhu commented 1 year ago

Problem

I am using NvChad with the following lspconfig for ruby_ls.

lspconfig.ruby_ls.setup {
  on_attach = function(client, buffer)
    local callback = function()
      local params = vim.lsp.util.make_text_document_params(buffer)

      client.request(
        'textDocument/diagnostic',
        { textDocument = params },
        function(err, result)
          if err then return end

          vim.lsp.diagnostic.on_publish_diagnostics(
            nil,
            vim.tbl_extend('keep', params, { diagnostics = result.items }),
            { client_id = client.id }
          )
        end
      )
    end

    callback() -- call on attach

    vim.api.nvim_create_autocmd({ 'BufEnter', 'BufWritePre', 'BufReadPost', 'InsertLeave', 'TextChanged' }, {
      buffer = buffer,
      callback = callback,
    })
  end,
  capabilities = capabilities,
}

I added the workaround to make diagnostics work in neovim but there is an error when i make a change in a .rb file:

Error executing vim.schedule lua callback: /home/adhu/.config/nvim/lua/custom/configs/lspconfig.lua:67: attempt to index local 'result' (a nil value)

:LspInfo

image

vinistock commented 1 year ago

Closing this since instructions were added in #830. If there are other instructions for different NeoVim plugins that can connect to the LSP, please feel free to put up a PR adding the configuration to a new section in that file.

staycreativedesign commented 8 months ago

I just wanted to hop in and drop my neovim config that I've been using for a little while:

local lspconfig = require("lspconfig")
local configs = require("lspconfig.configs")
local util = require("lspconfig.util")

if not configs.ruby_lsp then
  local enabled_features = {
      "documentHighlights",
      "documentSymbols",
      "foldingRanges",
      "selectionRanges",
      -- "semanticHighlighting",
      "formatting",
      "codeActions",
  }

  configs.ruby_lsp = {
      default_config = {
          cmd = { "bundle", "exec", "ruby-lsp" },
          filetypes = { "ruby" },
          root_dir = util.root_pattern("Gemfile", ".git"),
          init_options = {
              enabledFeatures = enabled_features,
          },
          settings = {},
      },
      commands = {
          FormatRuby = {
              function()
                  vim.lsp.buf.format({
                      name = "ruby_lsp",
                      async = true,
                  })
              end,
              description = "Format using ruby-lsp",
          },
      },
  }
end

lspconfig.ruby_lsp.setup({ on_attach = on_attach, capabilities = capabilities })

I hope this helps!

How would I lazy load this using return { } ?

wassimk commented 3 months ago

How would I lazy load this using return { } ?

I need help understanding the question. The LSP will only start in Ruby files. That should be the required lazy loading.