jmederosalvarado / roslyn.nvim

Roslyn LSP plugin for neovim
MIT License
191 stars 33 forks source link

Support LSP semanticTokens #34

Open StefanFanaru opened 1 month ago

StefanFanaru commented 1 month ago

I know that this new LSP from Microsoft supports semantic tokens, but the usage of this library in neovim is not able to deliver them. Is there a way to enable them?

What I underlined with red should have been colored with red because they are parameters. What I underlined with yellow should have been yellow because they are Types. image

daver32 commented 4 weeks ago

It looks like nvim and roslyn don't yet support a common request type when it comes to semantic highlights: https://github.com/neovim/neovim/pull/26500 https://github.com/dotnet/roslyn/issues/70536

daver32 commented 4 weeks ago

Here's a little hack you can put in your on_attach to get semantic tokens:

on_attach = function(client)
  -- make sure this happens once per client, not per buffer
  if not client.is_hacked then
    client.is_hacked = true

    -- let the runtime know the server can do semanticTokens/full now
    client.server_capabilities = vim.tbl_deep_extend("force", client.server_capabilities, {
      semanticTokensProvider = {
        full = true,
      },
    })

    -- monkey patch the request proxy
    local request_inner = client.request
    client.request = function(method, params, handler)
      if method ~= vim.lsp.protocol.Methods.textDocument_semanticTokens_full then
        return request_inner(method, params, handler)
      end

      local function find_buf_by_uri(search_uri)
        local bufs = vim.api.nvim_list_bufs()
        for _, buf in ipairs(bufs) do
          local name = vim.api.nvim_buf_get_name(buf)
          local uri = "file://" .. name
          if uri == search_uri then
            return buf
          end
        end
      end

      local doc_uri = params.textDocument.uri

      local target_bufnr = find_buf_by_uri(doc_uri)
      local line_count = vim.api.nvim_buf_line_count(target_bufnr)
      local last_line = vim.api.nvim_buf_get_lines(target_bufnr, line_count - 1, line_count, true)[1]

      return request_inner("textDocument/semanticTokens/range", {
        textDocument = params.textDocument,
        range = {
          ["start"] = {
            line = 0,
            character = 0,
          },
          ["end"] = {
            line = line_count - 1,
            character = string.len(last_line) - 1,
          },
        },
      }, handler)
    end
  end
end

It translates textDocument/semanticTokens/full to textDocument/semanticTokens/range and back. I can put it in a PR later if wanted.

StefanFanaru commented 3 weeks ago

Thank you a lot for your reply and for the workaround. Unfortunatley I can't get it to work, maybe i'm missing something

I did it like this inside lsp config:

            require("roslyn").setup({
                dotnet_cmd = "dotnet",
                roslyn_version = "4.11.0-1.24209.10",
                on_attach = function(client)
                    -- make sure this happens once per client, not per buffer
                    if not client.is_hacked then
                        client.is_hacked = true

                        -- let the runtime know the server can do semanticTokens/full now
                        client.server_capabilities = vim.tbl_deep_extend("force", client.server_capabilities, {
                            semanticTokensProvider = {
                                full = true,
                            },
                        })

                        -- monkey patch the request proxy
                        local request_inner = client.request
                        client.request = function(method, params, handler)
                            if method ~= vim.lsp.protocol.Methods.textDocument_semanticTokens_full then
                                return request_inner(method, params, handler)
                            end

                            local function find_buf_by_uri(search_uri)
                                local bufs = vim.api.nvim_list_bufs()
                                for _, buf in ipairs(bufs) do
                                    local name = vim.api.nvim_buf_get_name(buf)
                                    local uri = "file://" .. name
                                    if uri == search_uri then
                                        return buf
                                    end
                                end
                            end

                            local doc_uri = params.textDocument.uri

                            local target_bufnr = find_buf_by_uri(doc_uri)
                            local line_count = vim.api.nvim_buf_line_count(target_bufnr)
                            local last_line =
                                vim.api.nvim_buf_get_lines(target_bufnr, line_count - 1, line_count, true)[1]

                            return request_inner("textDocument/semanticTokens/range", {
                                textDocument = params.textDocument,
                                range = {
                                    ["start"] = {
                                        line = 0,
                                        character = 0,
                                    },
                                    ["end"] = {
                                        line = line_count - 1,
                                        character = string.len(last_line) - 1,
                                    },
                                },
                            }, handler)
                        end
                    end
                end,
                capabilities = capabilities,
                settings = {
                    ["csharp|inlay_hints"] = {
                        csharp_enable_inlay_hints_for_lambda_parameter_types = true,
                        csharp_enable_inlay_hints_for_implicit_object_creation = true,
                        csharp_enable_inlay_hints_for_implicit_variable_types = true,
                        csharp_enable_inlay_hints_for_types = true,
                        dotnet_enable_inlay_hints_for_indexer_parameters = true,
                        dotnet_enable_inlay_hints_for_literal_parameters = true,
                        dotnet_enable_inlay_hints_for_object_creation_parameters = true,
                        dotnet_enable_inlay_hints_for_other_parameters = true,
                        dotnet_enable_inlay_hints_for_parameters = true,
                        dotnet_suppress_inlay_hints_for_parameters_that_differ_only_by_suffix = true,
                        dotnet_suppress_inlay_hints_for_parameters_that_match_argument_name = true,
                        dotnet_suppress_inlay_hints_for_parameters_that_match_method_intent = true,
                    },
                },
            })
Crashdummyy commented 3 days ago

Any update on this one ?

It used to work using that config for a while but now it suddenly stopped.

  {
    "seblj/roslyn.nvim",
    event = "User csFile",
    config = function()
      require("roslyn").setup({
        dotnet_cmd = "dotnet", -- this is the default
        roslyn_version = "4.12.0-1.24329.2", -- this is the default
        on_attach = function(client)
  -- make sure this happens once per client, not per buffer
  if not client.is_hacked then
    client.is_hacked = true

    -- let the runtime know the server can do semanticTokens/full now
    client.server_capabilities = vim.tbl_deep_extend("force", client.server_capabilities, {
      semanticTokensProvider = {
        full = true,
      },
    })

    -- monkey patch the request proxy
    local request_inner = client.request
    client.request = function(method, params, handler)
      if method ~= vim.lsp.protocol.Methods.textDocument_semanticTokens_full then
        return request_inner(method, params, handler)
      end

      local function find_buf_by_uri(search_uri)
        local bufs = vim.api.nvim_list_bufs()
        for _, buf in ipairs(bufs) do
          local name = vim.api.nvim_buf_get_name(buf)
          local uri = "file://" .. name
          if uri == search_uri then
            return buf
          end
        end
      end

      local doc_uri = params.textDocument.uri

      local target_bufnr = find_buf_by_uri(doc_uri)
      local line_count = vim.api.nvim_buf_line_count(target_bufnr)
      local last_line = vim.api.nvim_buf_get_lines(target_bufnr, line_count - 1, line_count, true)[1]

      return request_inner("textDocument/semanticTokens/range", {
        textDocument = params.textDocument,
        range = {
          ["start"] = {
            line = 0,
            character = 0,
          },
          ["end"] = {
            line = line_count - 1,
            character = string.len(last_line) - 1,
          },
        },
      }, handler)
    end
  end
end
      })
    end
  },

EDIT: Okay it apppears like on_attach is not invoked anymore. So I guess once I get this done it'll start working again. EDIT2: The latest update fixes this issue :)

Crashdummyy commented 3 days ago

Here's a little hack you can put in your on_attach to get semantic tokens:

on_attach = function(client)
  -- make sure this happens once per client, not per buffer
  if not client.is_hacked then
    client.is_hacked = true

    -- let the runtime know the server can do semanticTokens/full now
    client.server_capabilities = vim.tbl_deep_extend("force", client.server_capabilities, {
      semanticTokensProvider = {
        full = true,
      },
    })

    -- monkey patch the request proxy
    local request_inner = client.request
    client.request = function(method, params, handler)
      if method ~= vim.lsp.protocol.Methods.textDocument_semanticTokens_full then
        return request_inner(method, params, handler)
      end

      local function find_buf_by_uri(search_uri)
        local bufs = vim.api.nvim_list_bufs()
        for _, buf in ipairs(bufs) do
          local name = vim.api.nvim_buf_get_name(buf)
          local uri = "file://" .. name
          if uri == search_uri then
            return buf
          end
        end
      end

      local doc_uri = params.textDocument.uri

      local target_bufnr = find_buf_by_uri(doc_uri)
      local line_count = vim.api.nvim_buf_line_count(target_bufnr)
      local last_line = vim.api.nvim_buf_get_lines(target_bufnr, line_count - 1, line_count, true)[1]

      return request_inner("textDocument/semanticTokens/range", {
        textDocument = params.textDocument,
        range = {
          ["start"] = {
            line = 0,
            character = 0,
          },
          ["end"] = {
            line = line_count - 1,
            character = string.len(last_line) - 1,
          },
        },
      }, handler)
    end
  end
end

It translates textDocument/semanticTokens/full to textDocument/semanticTokens/range and back. I can put it in a PR later if wanted.

This monkeypatch appears to work in my case but I've got one weird problem. Whenever I open a file I need to toggle the syntax on and off for it to work.

image

image

iamim commented 1 day ago

Here's a little hack you can put in your on_attach to get semantic tokens:

on_attach = function(client)
  -- make sure this happens once per client, not per buffer
  if not client.is_hacked then
    client.is_hacked = true

    -- let the runtime know the server can do semanticTokens/full now
    client.server_capabilities = vim.tbl_deep_extend("force", client.server_capabilities, {
      semanticTokensProvider = {
        full = true,
      },
    })

    -- monkey patch the request proxy
    local request_inner = client.request
    client.request = function(method, params, handler)
      if method ~= vim.lsp.protocol.Methods.textDocument_semanticTokens_full then
        return request_inner(method, params, handler)
      end

      local function find_buf_by_uri(search_uri)
        local bufs = vim.api.nvim_list_bufs()
        for _, buf in ipairs(bufs) do
          local name = vim.api.nvim_buf_get_name(buf)
          local uri = "file://" .. name
          if uri == search_uri then
            return buf
          end
        end
      end

      local doc_uri = params.textDocument.uri

      local target_bufnr = find_buf_by_uri(doc_uri)
      local line_count = vim.api.nvim_buf_line_count(target_bufnr)
      local last_line = vim.api.nvim_buf_get_lines(target_bufnr, line_count - 1, line_count, true)[1]

      return request_inner("textDocument/semanticTokens/range", {
        textDocument = params.textDocument,
        range = {
          ["start"] = {
            line = 0,
            character = 0,
          },
          ["end"] = {
            line = line_count - 1,
            character = string.len(last_line) - 1,
          },
        },
      }, handler)
    end
  end
end

It translates textDocument/semanticTokens/full to textDocument/semanticTokens/range and back. I can put it in a PR later if wanted.

This monkeypatch appears to work in my case but I've got one weird problem. Whenever I open a file I need to toggle the syntax on and off for it to work.

image

image

Could it have something to do with the lazy events you load your LSP with?

Crashdummyy commented 1 day ago

No I started clean. Maybe it's something weird with my solution I dont konw.

Yaml files started having this issue as well. Theres something in this directories really messing up treesitter but I think I'll get it handled.

Btw I started uploading the lsp builds to a github repository once a day if that is of any interest to you