pygls icon indicating copy to clipboard operation
pygls copied to clipboard

How to make `cmd` + `click` switch between `Go to definition` and `Go to reference`? (VSCode only)

Open noklam opened this issue 1 year ago • 3 comments
trafficstars

Context: https://github.com/microsoft/vscode/issues/73081#issuecomment-557491699

In the Python extension, Cmd + Click trigger Go to Definition or Go to reference depending if the click is on a declaration or reference.

I have this pseudocode and it doesn't seem to trigger the action correctly. p.s. the language server is working properly, the example is simplified.

@LSP_SERVER.feature(TEXT_DOCUMENT_DEFINITION)
def definition(
    ls, params: TextDocumentPositionParams
):
   uri = params.text_document.uri
   pos = params.position
   curr_pos =  Position(line= pos.line, character=pos.character)
   return Location(uri = uri, range = Location (Range(start=curr_pos, end=curr_pos))

alternatively, I have tried:

@LSP_SERVER.feature(TEXT_DOCUMENT_DEFINITION)
def definition(ls, params):
  return params

p.s. I am not so sure What's the difference between TextDocumentPositionParams and a Location

noklam avatar May 03 '24 17:05 noklam

I am not so sure What's the difference between TextDocumentPositionParams and a Location

The main difference is that TextDocumentPositionParams contains a Position and therefore describes a single location within the document e.g. where the cursor is. Whereas Location contains a Range which describes a region of text within the document e.g. the text you highlight just before you copy it.

I have this pseudocode and it doesn't seem to trigger the action correctly.

I think you are close, though you have an extra Location in your return statement, something like the following should work

return Location(uri=uri, range=Range(start=curr_pos, end=curr_pos))

Though I'm not sure how VSCode handles a zero-length range, it might not be obvious that it did anything, you may also want to try something like this

return Location(
    uri=uri, 
    range=Range(
        start=Position(line=pos.line, character=0), 
        end=Position(line=pos.line + 1, character=0)
    )
)

Now when you trigger Goto Definition, VSCode should highlight the whole line

In the Python extension, Cmd + Click trigger Go to Definition or Go to reference depending if the click is on a declaration or reference.

Unfortunately, I don't know for sure how to make this work - the LSP spec tries hard not to dictate how the UI for each method should work. The server provides the data and it's up to the client to decide how to show it.

However, I would start by also implementing TEXT_DOCUMENT_REFERENCES to return two Locations, one being the definition itself, the other being some other location in the document and see how VSCode handles it.

Hope that helps!

alcarney avatar May 03 '24 22:05 alcarney

2024-05-07_12-53-11 (1)

return Location(uri=uri, range=Range(start=curr_pos, end=curr_pos)), I think my previous implementation has a bug as you have noticed. Once I return the current cursor it seems to behave as I expected.

Thanks a lot for the help! This issue can be closed

Unfortunately, I don't know for sure how to make this work - the LSP spec tries hard not to dictate how the UI for each method should work. The server provides the data and it's up to the client to decide how to show it.

You are correct that this is up to the client. I asked because

fyi - I have pushed a commit that adds settings like editor.gotoLocation.alternativeDefinitionCommand etc. They allow to define what happens when the corresponding go-to-command navigates to the current location https://github.com/microsoft/vscode/issues/73081#issuecomment-557491699

It's not clear to me whether "current location" is Location or if I can return an exact position like TextDocumentPositionParams. Is it possible if my LSP return TextDocumentPositionParams instead of Location? I assume ultimately they all get converted to a standard format (JSON like I assume)

noklam avatar May 07 '24 11:05 noklam

I assume ultimately they all get converted to a standard format (JSON like I assume)

Everything in LSP is JSON under the hood, types like Location are just names given to a well defined JSON object with a specific structure

or if I can return an exact position like TextDocumentPositionParams.

As a general rule anything with Params in the name can't be sent as a response to a request.

The LSP specification itself is probably the best place to look for answers to questions like "what can I return here?" Admittedly, it's not the easiest document to navigate, but the information is there. Taking your Goto Definition request as an example, if you scroll down to just before the Goto Type Definition Request heading, you should see a Response section.

Under it the spec lists the valid responses to the request separated by | characters

result: Location | Location[] | LocationLink[] | null

alcarney avatar May 07 '24 21:05 alcarney

I forgot to respond to this. For VSCode speficially, you need two things:

  1. Return location to the same position as the current cursor, this is how I done it.
    uri = params.text_document.uri
    pos = params.position
    curr_pos = Position(line=pos.line, character=pos.character)
    return Location(uri=uri, range=Range(start=curr_pos, end=curr_pos))
  1. Make sure the "Alternative Definition Command" trigger "Go to Reference" image

This is a VSCode specific thing, it basically tells VSCode if it receives a definition from LSP that return to the same location, trigger the other action. This sounds weird at first, but this support a smooth UX (confusing to some people, thus you can change the setting) that you can keep doing cmd + click to jump between reference and definition.

https://github.com/kedro-org/vscode-kedro/blob/2bccbd80014772c444fdc64beca53371287aa8b1/bundled/tool/lsp_server.py#L310-L313

noklam avatar Aug 13 '24 21:08 noklam