erlang-ls / erlang_ls

The Erlang Language Server
https://erlang-ls.github.io/
Apache License 2.0
630 stars 136 forks source link

Jump To Definition for Common Test Groups #1204

Closed robertoaloi closed 2 years ago

robertoaloi commented 2 years ago

Context

One of the core features provided by Erlang LS is code navigation, which allows the user to jump from the current location to the corresponding definition. This mechanism works for functions, macros, records and more. The way code navigation works is by means of a database of points of interest which Erlang LS keeps in memory for each project. When accessing a project for the first time, Erlang LS scans every file in the project and extracts all point of interests (e.g. a function definition, or a function application), together with their location (file path, starting line number, starting column number, ending line number, ending column number, etc).

Whenever a Go To Definition Request is sent from the IDE to the language server, Erlang LS looks up in the database the corresponding entry and returns the appropriate result.

Here's a sample request from the IDE:

[Trace - 05:25:03 PM] Sending request 'textDocument/definition - (5794)'.
Params: {
  "textDocument": {
    "uri": "file:///Users/robertoaloi/git/github/erlang-ls/erlang_ls/apps/els_lsp/src/els_code_navigation.erl"
  },
  "position": {
    "line": 51,
    "character": 9
  }
}

The user, while visiting the els_code_navigation.erl file, requested a "go to definition" while in position 51:9. In VS Code this is usually done by pressing Cmd+click.

If we peek at the actual code in els_code_navigation.erl, we can see that in this case the user was hovering a function application (i.e. a function call):

Screenshot 2022-02-16 at 17 31 54

Notice how in the protocol, compared to the IDE, line numbers are offset by 1, so line 52 in the editor corresponds to line 51 in the request.

Once received the request, the server interrogates its own DB and realizes that the location points to the local function call to find/3. It then performs an additional query, looking for the definition of the find/3 function. The function is located on the same file (or Uri) on line 129, so the following response can be sent.

[Trace - 05:25:03 PM] Received response 'textDocument/definition - (5794)' in 1ms.
Result: {
  "uri": "file:///Users/robertoaloi/git/github/erlang-ls/erlang_ls/apps/els_lsp/src/els_code_navigation.erl",
  "range": {
    "start": {
      "line": 128,
      "character": 0
    },
    "end": {
      "line": 128,
      "character": 4
    }
  }
}

What is required

When a textDocument/definition is performed on an atom, it is handled in a special way. Atoms in Erlang are constant literals, so it does not make too much sense to look for an atom definition per se. But atoms can also be used in special contexts. For example, Erlang module names are also atoms. Therefore, whenever the user tries to use the jump-to-definition functionality on an atom, we decided to jump to the respective module definition if that's available. That's not always desirable, though.

Imagine to be working on a Common Test suite (Common Test is one of the main testing frameworks for Erlang). You may end up with code that looks like the following:

groups() -> [{group_1, [parallel], [testcase_1]}].
all() -> [{group, group_1}].

When invoking the "go-to-definition" on the atom group_1 we want to jump to the CT group called group_1 and not to the group_1 module!

Code Pointers

Testing

Don't forget to include a test for the new functionality! You can look at existing examples in the els_definition_SUITE module and at this code lenses tutorial which includes a testing overview for Erlang LS.

Problems following the instructions?

Get in touch! We'll be glad to help you to get unblocked and to improve our docs.

robertoaloi commented 2 years ago

Closing this as a won't implement. Groups are essentially tagged tuples and the effort of getting this right is probably not worth. In case of complex suites, it should be possible to define a wrapper function for groups and then get navigation from atoms out of the box.