mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +00:00 
			
		
		
		
	fix(lsp): support multiple clients in typehierarchy
This commit is contained in:
		
				
					committed by
					
						
						Lewis Russell
					
				
			
			
				
	
			
			
			
						parent
						
							4c25e60767
						
					
				
				
					commit
					629a5b71b5
				
			@@ -2677,7 +2677,7 @@ vim.ui.select({items}, {opts}, {on_choice})                  *vim.ui.select()*
 | 
				
			|||||||
                       item shape. Plugins reimplementing `vim.ui.select` may
 | 
					                       item shape. Plugins reimplementing `vim.ui.select` may
 | 
				
			||||||
                       wish to use this to infer the structure or semantics of
 | 
					                       wish to use this to infer the structure or semantics of
 | 
				
			||||||
                       `items`, or the context in which select() was called.
 | 
					                       `items`, or the context in which select() was called.
 | 
				
			||||||
      • {on_choice}  (`fun(item: any?, idx: integer?)`) Called once the user
 | 
					      • {on_choice}  (`fun(item: T?, idx: integer?)`) Called once the user
 | 
				
			||||||
                     made a choice. `idx` is the 1-based index of `item`
 | 
					                     made a choice. `idx` is the 1-based index of `item`
 | 
				
			||||||
                     within `items`. `nil` if the user aborted the dialog.
 | 
					                     within `items`. `nil` if the user aborted the dialog.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -86,6 +86,8 @@ LSP
 | 
				
			|||||||
  |vim.lsp.buf.type_definition()| and |vim.lsp.buf.implementation()| now
 | 
					  |vim.lsp.buf.type_definition()| and |vim.lsp.buf.implementation()| now
 | 
				
			||||||
  support merging the results of multiple clients but no longer trigger the
 | 
					  support merging the results of multiple clients but no longer trigger the
 | 
				
			||||||
  global handlers from `vim.lsp.handlers`
 | 
					  global handlers from `vim.lsp.handlers`
 | 
				
			||||||
 | 
					• |vim.lsp.buf.typehierarchy()| now passes the correct params for each
 | 
				
			||||||
 | 
					  client request.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
LUA
 | 
					LUA
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -569,6 +569,23 @@ function M.document_symbol(opts)
 | 
				
			|||||||
  request_with_opts(ms.textDocument_documentSymbol, params, opts)
 | 
					  request_with_opts(ms.textDocument_documentSymbol, params, opts)
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--- @param client_id integer
 | 
				
			||||||
 | 
					--- @param method string
 | 
				
			||||||
 | 
					--- @param params table
 | 
				
			||||||
 | 
					--- @param handler? lsp.Handler
 | 
				
			||||||
 | 
					--- @param bufnr? integer
 | 
				
			||||||
 | 
					local function request_with_id(client_id, method, params, handler, bufnr)
 | 
				
			||||||
 | 
					  local client = vim.lsp.get_client_by_id(client_id)
 | 
				
			||||||
 | 
					  if not client then
 | 
				
			||||||
 | 
					    vim.notify(
 | 
				
			||||||
 | 
					      string.format('Client with id=%d disappeared during call hierarchy request', client_id),
 | 
				
			||||||
 | 
					      vim.log.levels.WARN
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					  client.request(method, params, handler, bufnr)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
--- @param call_hierarchy_items lsp.CallHierarchyItem[]
 | 
					--- @param call_hierarchy_items lsp.CallHierarchyItem[]
 | 
				
			||||||
--- @return lsp.CallHierarchyItem?
 | 
					--- @return lsp.CallHierarchyItem?
 | 
				
			||||||
local function pick_call_hierarchy_item(call_hierarchy_items)
 | 
					local function pick_call_hierarchy_item(call_hierarchy_items)
 | 
				
			||||||
@@ -600,19 +617,11 @@ local function call_hierarchy(method)
 | 
				
			|||||||
      vim.notify('No item resolved', vim.log.levels.WARN)
 | 
					      vim.notify('No item resolved', vim.log.levels.WARN)
 | 
				
			||||||
      return
 | 
					      return
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
    local call_hierarchy_item = pick_call_hierarchy_item(result)
 | 
					    local item = pick_call_hierarchy_item(result)
 | 
				
			||||||
    if not call_hierarchy_item then
 | 
					    if not item then
 | 
				
			||||||
      return
 | 
					      return
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
    local client = vim.lsp.get_client_by_id(ctx.client_id)
 | 
					    request_with_id(ctx.client_id, method, { item = item }, nil, ctx.bufnr)
 | 
				
			||||||
    if client then
 | 
					 | 
				
			||||||
      client.request(method, { item = call_hierarchy_item }, nil, ctx.bufnr)
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
      vim.notify(
 | 
					 | 
				
			||||||
        string.format('Client with id=%d disappeared during call hierarchy request', ctx.client_id),
 | 
					 | 
				
			||||||
        vim.log.levels.WARN
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
  end)
 | 
					  end)
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -630,78 +639,74 @@ function M.outgoing_calls()
 | 
				
			|||||||
  call_hierarchy(ms.callHierarchy_outgoingCalls)
 | 
					  call_hierarchy(ms.callHierarchy_outgoingCalls)
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--- @param item lsp.TypeHierarchyItem
 | 
				
			||||||
 | 
					local function format_type_hierarchy_item(item)
 | 
				
			||||||
 | 
					  if not item.detail or #item.detail == 0 then
 | 
				
			||||||
 | 
					    return item.name
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					  return string.format('%s %s', item.name, item.detail)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
--- Lists all the subtypes or supertypes of the symbol under the
 | 
					--- Lists all the subtypes or supertypes of the symbol under the
 | 
				
			||||||
--- cursor in the |quickfix| window. If the symbol can resolve to
 | 
					--- cursor in the |quickfix| window. If the symbol can resolve to
 | 
				
			||||||
--- multiple items, the user can pick one using |vim.ui.select()|.
 | 
					--- multiple items, the user can pick one using |vim.ui.select()|.
 | 
				
			||||||
---@param kind "subtypes"|"supertypes"
 | 
					---@param kind "subtypes"|"supertypes"
 | 
				
			||||||
function M.typehierarchy(kind)
 | 
					function M.typehierarchy(kind)
 | 
				
			||||||
  local method = kind == 'subtypes' and ms.typeHierarchy_subtypes or ms.typeHierarchy_supertypes
 | 
					  local method = kind == 'subtypes' and ms.typeHierarchy_subtypes or ms.typeHierarchy_supertypes
 | 
				
			||||||
 | 
					 | 
				
			||||||
  --- Merge results from multiple clients into a single table. Client-ID is preserved.
 | 
					 | 
				
			||||||
  ---
 | 
					 | 
				
			||||||
  --- @param results table<integer, {error: lsp.ResponseError?, result: lsp.TypeHierarchyItem[]?}>
 | 
					 | 
				
			||||||
  --- @return [integer, lsp.TypeHierarchyItem][]
 | 
					 | 
				
			||||||
  local function merge_results(results)
 | 
					 | 
				
			||||||
    local merged_results = {}
 | 
					 | 
				
			||||||
    for client_id, client_result in pairs(results) do
 | 
					 | 
				
			||||||
      if client_result.error then
 | 
					 | 
				
			||||||
        vim.notify(client_result.error.message, vim.log.levels.WARN)
 | 
					 | 
				
			||||||
      elseif client_result.result then
 | 
					 | 
				
			||||||
        for _, item in pairs(client_result.result) do
 | 
					 | 
				
			||||||
          table.insert(merged_results, { client_id, item })
 | 
					 | 
				
			||||||
        end
 | 
					 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
    return merged_results
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  local bufnr = api.nvim_get_current_buf()
 | 
					  local bufnr = api.nvim_get_current_buf()
 | 
				
			||||||
  local params = util.make_position_params()
 | 
					  local clients = vim.lsp.get_clients({ bufnr = bufnr, method = method })
 | 
				
			||||||
  --- @param results table<integer, {error: lsp.ResponseError?, result: lsp.TypeHierarchyItem[]?}>
 | 
					  if not next(clients) then
 | 
				
			||||||
  vim.lsp.buf_request_all(bufnr, ms.textDocument_prepareTypeHierarchy, params, function(results)
 | 
					    vim.notify(vim.lsp._unsupported_method(method), vim.log.levels.WARN)
 | 
				
			||||||
    local merged_results = merge_results(results)
 | 
					 | 
				
			||||||
    if #merged_results == 0 then
 | 
					 | 
				
			||||||
      vim.notify('No items resolved', vim.log.levels.INFO)
 | 
					 | 
				
			||||||
    return
 | 
					    return
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if #merged_results == 1 then
 | 
					  local win = api.nvim_get_current_win()
 | 
				
			||||||
      local item = merged_results[1]
 | 
					
 | 
				
			||||||
      local client = vim.lsp.get_client_by_id(item[1])
 | 
					  --- @param results [integer, lsp.TypeHierarchyItem][]
 | 
				
			||||||
      if client then
 | 
					  local function on_response(results)
 | 
				
			||||||
        client.request(method, { item = item[2] }, nil, bufnr)
 | 
					    if #results == 0 then
 | 
				
			||||||
 | 
					      vim.notify('No items resolved', vim.log.levels.INFO)
 | 
				
			||||||
 | 
					    elseif #results == 1 then
 | 
				
			||||||
 | 
					      local client_id, item = results[1][1], results[1][2]
 | 
				
			||||||
 | 
					      request_with_id(client_id, method, { item = item }, nil, bufnr)
 | 
				
			||||||
    else
 | 
					    else
 | 
				
			||||||
        vim.notify(
 | 
					      vim.ui.select(results, {
 | 
				
			||||||
          string.format('Client with id=%d disappeared during call hierarchy request', item[1]),
 | 
					 | 
				
			||||||
          vim.log.levels.WARN
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
      local select_opts = {
 | 
					 | 
				
			||||||
        prompt = 'Select a type hierarchy item:',
 | 
					        prompt = 'Select a type hierarchy item:',
 | 
				
			||||||
        kind = 'typehierarchy',
 | 
					        kind = 'typehierarchy',
 | 
				
			||||||
        format_item = function(item)
 | 
					        format_item = function(x)
 | 
				
			||||||
          if not item[2].detail or #item[2].detail == 0 then
 | 
					          return format_type_hierarchy_item(x[2])
 | 
				
			||||||
            return item[2].name
 | 
					 | 
				
			||||||
          end
 | 
					 | 
				
			||||||
          return string.format('%s %s', item[2].name, item[2].detail)
 | 
					 | 
				
			||||||
        end,
 | 
					        end,
 | 
				
			||||||
      }
 | 
					      }, function(x)
 | 
				
			||||||
 | 
					        if x then
 | 
				
			||||||
 | 
					          local client_id, item = x[1], x[2]
 | 
				
			||||||
 | 
					          request_with_id(client_id, method, { item = item }, nil, bufnr)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      vim.ui.select(merged_results, select_opts, function(item)
 | 
					  local results = {} --- @type [integer, lsp.TypeHierarchyItem][]
 | 
				
			||||||
        local client = vim.lsp.get_client_by_id(item[1])
 | 
					
 | 
				
			||||||
        if client then
 | 
					  local remaining = #clients
 | 
				
			||||||
          --- @type lsp.TypeHierarchyItem
 | 
					
 | 
				
			||||||
          client.request(method, { item = item[2] }, nil, bufnr)
 | 
					  for _, client in ipairs(clients) do
 | 
				
			||||||
        else
 | 
					    local params = util.make_position_params(win, client.offset_encoding)
 | 
				
			||||||
          vim.notify(
 | 
					    client.request(method, params, function(err, result, ctx)
 | 
				
			||||||
            string.format('Client with id=%d disappeared during call hierarchy request', item[1]),
 | 
					      --- @cast result lsp.TypeHierarchyItem[]?
 | 
				
			||||||
            vim.log.levels.WARN
 | 
					      if err then
 | 
				
			||||||
          )
 | 
					        vim.notify(err.message, vim.log.levels.WARN)
 | 
				
			||||||
 | 
					      elseif result then
 | 
				
			||||||
 | 
					        for _, item in pairs(result) do
 | 
				
			||||||
 | 
					          results[#results + 1] = { ctx.client_id, item }
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
      end)
 | 
					 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
  end)
 | 
					
 | 
				
			||||||
 | 
					      remaining = remaining - 1
 | 
				
			||||||
 | 
					      if remaining == 0 then
 | 
				
			||||||
 | 
					        on_response(results)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end, bufnr)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
--- List workspace folders.
 | 
					--- List workspace folders.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,8 @@ local M = {}
 | 
				
			|||||||
--- end)
 | 
					--- end)
 | 
				
			||||||
--- ```
 | 
					--- ```
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
---@param items any[] Arbitrary items
 | 
					---@generic T
 | 
				
			||||||
 | 
					---@param items T[] Arbitrary items
 | 
				
			||||||
---@param opts table Additional options
 | 
					---@param opts table Additional options
 | 
				
			||||||
---     - prompt (string|nil)
 | 
					---     - prompt (string|nil)
 | 
				
			||||||
---               Text of the prompt. Defaults to `Select one of:`
 | 
					---               Text of the prompt. Defaults to `Select one of:`
 | 
				
			||||||
@@ -32,7 +33,7 @@ local M = {}
 | 
				
			|||||||
---               Plugins reimplementing `vim.ui.select` may wish to
 | 
					---               Plugins reimplementing `vim.ui.select` may wish to
 | 
				
			||||||
---               use this to infer the structure or semantics of
 | 
					---               use this to infer the structure or semantics of
 | 
				
			||||||
---               `items`, or the context in which select() was called.
 | 
					---               `items`, or the context in which select() was called.
 | 
				
			||||||
---@param on_choice fun(item: any|nil, idx: integer|nil)
 | 
					---@param on_choice fun(item: T|nil, idx: integer|nil)
 | 
				
			||||||
---               Called once the user made a choice.
 | 
					---               Called once the user made a choice.
 | 
				
			||||||
---               `idx` is the 1-based index of `item` within `items`.
 | 
					---               `idx` is the 1-based index of `item` within `items`.
 | 
				
			||||||
---               `nil` if the user aborted the dialog.
 | 
					---               `nil` if the user aborted the dialog.
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user