Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(lsp): correct handling of find_references (#3118) #3120

Closed

Conversation

odysseus0
Copy link

@odysseus0 odysseus0 commented May 20, 2024

Description

This PR resolves a bug reported in #3118 in the list_or_jump function of the find_references feature for the default option "excluding the current position". Previously, the function incorrectly assumed that the locations and items tables were in the same order, which is not the case as vim.lsp.util.locations_to_items sorts items. This resulted in incorrect exclusions, affecting the functionality when handling multiple references (wrong exclusion) or a single reference (not jumping at all).

Changes Introduced

  • Implemented a sorting mechanism for locations based on the order of items returned by vim.lsp.util.locations_to_items.
  • The sorting is based on a stable identifier generated from both locations and items, ensuring that they match correctly by index.

Rationale

The chosen method leverages the output from vim.lsp.util.locations_to_items to ensure that locations are reordered to match the sorted items. This approach treats the Neovim utility as a black box and only assumes the stability of location and item type definitions, such as lsp.Location and lsp.LocationLink, so that it won't depend on the exact implementation of the utility.

The alternative approach would be replicating the sorting logics in vim.lsp.util.locations_to_items, but that would break the abstraction of neovim utility and increases risk of the implementation breaking due to internal changes in the vim.lsp.util.locations_to_items function.

This update ensures that the list_or_jump function behaves as expected, particularly in scenarios where the current location should be excluded from find_references results.

Testing

Use the same config in #3118.

Below is a minimal example that tests the functionality.

package main

import "fmt"

func main() {
	hello()

	a := 1
	fmt.Println(a)
	fmt.Println(a + 1)
}

func hello() {
	fmt.Println("Hello, World!")
}

It now correctly jumps to the reference in the above example for single reference hello. Before it does not work.

It also properly lists both references for a for the case of multiple references.

Checklist

  • My code follows the style guidelines of this project (stylua)
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (lua annotations)

There is a bug in list_or_jump implementation. The intended behavior is
to drop the item that corresponds to the current location when the
option is to not include the current location in the list.

However, a bug in implementation assumes that the locations and items
list are in the same order, which is not the case, as location_to_items
implementation sorts the items. So the result is that often the wrong
item got dropped. When there are more than 1 references, the behavior
would be potentially missing reference. When there is 1 reference, it
could be potentially not jumping at all when the reference instead of
the definiion got dropped.

This fix takes a table of items sorts the locations in the same order as
items. It has O(N) complexity where N is the size of the items table.
@odysseus0 odysseus0 marked this pull request as ready for review May 20, 2024 04:49
@odysseus0 odysseus0 changed the title fix(lsp): correct handling of find_references fix(lsp): correct handling of find_references (#3118) May 20, 2024
@odysseus0
Copy link
Author

odysseus0 commented May 20, 2024

Just got started using Neovim last Friday. Still a newbie. Sorry if some part of the PR is not up to best practice. Feedback more than welcomed!

Thank you guys so much for making these killer plugins.

@odysseus0
Copy link
Author

Wondering what else I need to do to get the PR merged

@Conni2461
Copy link
Member

4 days open, give me at least time to look at it and try to understand the problem why it doesnt work. this code has been modified much more often than I would like and there is always the next PR. i actually need to think about this and do some testing. be patient, i will look at it. Thanks for understanding :)

@adriangoransson
Copy link
Contributor

4 days open, give me at least time to look at it and try to understand the problem why it doesnt work. this code has been modified much more often than I would like and there is always the next PR. i actually need to think about this and do some testing. be patient, i will look at it. Thanks for understanding :)

To give you some context, these might be useful:

My take is that maybe #3099 maybe should be merged again, because just a few days after it was reverted 0.10 was released with the changes it depended on.

@Conni2461
Copy link
Member

Again it will just take a couple more days to get this issue fixed. #3099 will not be merged again, we still have 0.9 support.

@odysseus0
Copy link
Author

Don't mean to rush you at all! Just trying to figure out if there is something else I can or need to do on my end! Thank you so much for all your work.

@jamestrew
Copy link
Contributor

Sorry @Conni2461 I can take this one considering I sort of started this mess.

I'm thinking I want to implement something closer to #3099 but since we don't have access to the user_data field to get Location|LocationLink info, we can just build this data ourselves at least as a carryover till we drop 0.9 support.

--- convert `item` type back to something we pass to `vim.lsp.util.jump_to_location`
---@param item vim.lsp.util.locations_to_items.ret
---@param offset_encoding string|nil utf-8|utf-16|utf-32
---@return lsp.Location # not exactly the same, only important parts for `jump_to_location`
local function item_to_location(item, offset_encoding)
  local location = {
    uri = vim.uri_from_fname(item.filename),
    range = {
      start = {
        line = item.lnum,
      },
    },
  }

  -- TODO: real implementation will need to use `item.line` and offset_encoding to
  -- get the real start.character value
  location.range.start.character = item.col
  return location
end

-- ...
-- ...
-- many lines later where to use `jump_to_location

      local location = item_to_location(item, offset_encoding)
      vim.lsp.util.jump_to_location(location, offset_encoding, opts.reuse_win)
    else
      pickers
        .new(opts, {

I think this could be simpler than sorting Location|LocationLink items to match the order of items. And it let's us generally simplify the action_handler code.

I can flesh out this idea myself a little bit later tonight.

@jamestrew
Copy link
Contributor

jamestrew commented May 26, 2024

I can flesh out this idea myself a little bit later tonight.

here it is: #3137

@odysseus0
Copy link
Author

🤣 jesus I am being such an idiot. If I can generate the unique 1-1 mapping between them, that means I have enough information to just reconstruct the whole thing. Yes this approach makes more sense.

@Conni2461
Copy link
Member

would close this in favor of #3137 its the simpler implementation.

still thanks for looking into the issue :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants