Dynamically changing settings block for LSP

Hi, I want to be able to apply a new configuration for a languageserver that is defined in the settings {} block.
Use case:
yaml-language-server is configured like this:

require('lspconfig').yamlls.setup({
  on_attach = default_on_attach,
  capabilities = capabilities,
  settings = {
    yaml = {
      trace = {
        server = "verbose"
      },
      completion = true,
      format = {
        enable = true
      },
      validate = true,
      schemaStore = {
        enable = true,
        url = "https://www.schemastore.org/api/json/catalog.json",
      },
      schemas = {
        kubernetes = {
          "*role*.y*ml",
          "deploy.y*ml",
          "deployment.y*ml",
          "ingress.y*ml",
          "kubectl-edit-*",
          "pdb.y*ml",
          "pod.y*ml",
          "rbac.y*ml",
          "service.y*ml",
          "service*account.y*ml",
          "storageclass.y*ml",
          "svc.y*ml"
        }
      }
    }
  }
})

I want to be able to change the schemas block dynamically to apply a different schema to a loaded buffer, and then refresh the language server so the new schema would be applied, maybe even offer the complete list of schemas with Telescope.nvim.

I’m not sure how to dynamically change the configuration after it has loaded.
I read a bit about the on_init but not sure how I can use that for my use case. Also read a lot about configuring the LSP but didn’t found what I needed.
Any ideas?
Thanks.

Took me quite a while and it’s not complete but here’s some code:

schema-select.lua (this uses Telescope to show all schemas)

local pickers = require "telescope.pickers"
local finders = require "telescope.finders"
local actions = require "telescope.actions"
local action_state = require "telescope.actions.state"
local conf = require("telescope.config").values
local util = require 'lspconfig'.util

local M = {}

M.current_yaml_schema = "No YAML schema"

M._get_client = function()
  M.bufnr = vim.api.nvim_get_current_buf()
  M.uri = vim.uri_from_bufnr(M.bufnr)
  if vim.bo.filetype ~= "yaml" then return end
  if not M.client then
    M.client = util.get_active_client_by_name(M.bufnr, 'yamlls')
  end
  return M.client
end

M._load_all_schemas = function()
  local client = M._get_client()
  if not client then return end
  local params = { uri = M.uri }
  client.request('yaml/get/all/jsonSchemas', params, function(err, result, _, _)
    if err then
      return
    end
    if result then
      if vim.tbl_count(result) == 0 then
        return vim.notify('Schemas not loaded yet.')
      end
      M._open_telescope(result)
    end
  end)
end

M._telescope_action = function(prompt_bufnr, _)
  actions.select_default:replace(function()
    actions.close(prompt_bufnr)
    local selection = action_state.get_selected_entry()
    M._change_settings(selection.value)
  end)
  return true
end

M._change_settings = function(schema)
  local client = M._get_client()
  local previous_settings = client.config.settings
  for key, value in pairs(previous_settings.yaml.schemas) do
    if vim.tbl_islist(value) then
      for idx, value_value in pairs(value) do
        if value_value == M.uri or string.find(value_value, '*') then
          table.remove(previous_settings.yaml.schemas[key], idx)
        end
      end
    elseif value == M.uri or string.find(value, '*') then
      previous_settings.yaml.schemas[key] = nil
    end
  end
  local new_settings = vim.tbl_deep_extend('force', previous_settings, {
    yaml = {
      schemas = {
        [schema] = M.uri
      }
    }
  })
  client.config.settings = new_settings
  client.notify("workspace/didChangeConfiguration")
  vim.notify('Successfully applied schema ' .. schema)
end

M._open_telescope = function(schemas)
  local opts = {}
  return pickers.new(opts, {
    prompt_title = "Yaml Schemas",
    finder = finders.new_table {
      results = schemas,
      entry_maker = function(entry)
        local ret_obj = {
          value = entry.uri,
          display = entry.uri,
          ordinal = entry.uri
        }
        if entry.name then
          ret_obj.display = entry.name
          ret_obj.ordinal = entry.name
        end
        return ret_obj
      end
    },

    sorter = conf.generic_sorter(opts),
    attach_mappings = M._telescope_action,
  }):find()
end

M.schema_name_mappings = {
  ["https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.22.4-standalone-strict/all.json"] = "k8s-1.22.4"
}

M.select = function()
  M._get_client()
  if not M.client then return end
  M._load_all_schemas()
end

M.get_current_schema = function()
  local client = M._get_client()
  if not M.client or not M.uri then
    return ''
  end
  client.request('yaml/get/jsonSchema', { M.uri }, function(err, e)
    local current_schema
    if err then
      return
    end
    if e[0] ~= nil then
      current_schema = e[0].uri
    elseif e[1] ~= nil then
      current_schema = e[1].uri
    end
    if current_schema ~= nil then
      if M.schema_name_mappings[current_schema] then
        current_schema = M.schema_name_mappings[current_schema]
      else
        current_schema = current_schema:gsub('https://raw.githubusercontent.com/', '')
        current_schema = current_schema:gsub('https://json.schemastore.org/', '')
      end
    end
    if current_schema then
      M.current_yaml_schema = "YAML schema: " .. current_schema
    end
  end)
  return M.current_yaml_schema
end

M._get_client()

return M

When initializing the client:

require'lspconfig'.yamlls.setup({
  ...
  on_init = function()
    require('user.select-schema').get_client()
  end,
  ...
})

Then you can also add this to lualine:

lualine.setup({
  sections = {
    lualine_c = {
      "require'user.select-schema'.get_current_schema()"
    },
  }
})
2 Likes