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

Add workspaceSymbol capability #110

Merged
merged 13 commits into from
Feb 13, 2020
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This fork started when [Jake Becker's repository](https://github.com/JakeBecker/
- Code formatter
- Find references to functions and modules (Thanks to @mattbaker)
- Quick symbol lookup in file (Thanks to @mattbaker)
- Quick symbol lookup in workspace and stdlib (both Elixir and erlang) (@lukaszsamson)

![Screenshot](images/screenshot.png?raw=true)

Expand Down Expand Up @@ -83,6 +84,28 @@ You can control which warnings are shown using the `elixirLS.dialyzerWarnOpts` s

ElixirLS's Dialyzer integration uses internal, undocumented Dialyzer APIs, and so it won't be robust against changes to these APIs in future Erlang versions.

## Workspace Symbols

With Dialyzer integration enabled ElixirLS will build an index of symbols (modules, functions, types and callbacks). The symbols are taken from the current workspace, all dependencies and stdlib (Elixir and erlang). This feature enables quick navigation to symbol definitions. However due to sheer number of different symbols and fuzzy search utilized by the provider, ElixirLS uses query prefixes to improve search results relevance.

Use the following rules when navigating to workspace symbols:
* no prefix - search for modules
* `:erl`
* `Enu`
* `f ` prefix - search for functions
* `f inse`
* `f :ets.inse`
* `f Enum.cou`
* `f count/0`
* `t ` prefix - search for types
* `t t/0`
* `t :erlang.time_u`
* `t DateTime.`
* `c ` prefix - search for callbacks
* `c handle_info`
* `c GenServer.in`
* `c :gen_statem`

## Troubleshooting

Basic troubleshooting steps:
Expand All @@ -95,6 +118,7 @@ If your code doesn't compile in ElixirLS, it may be because ElixirLS compiles co
## Known Issues

* `.exs` files don't return compilation errors
* `workspaceSymbolProvider` capability currently requires enabled dialyzer

## Building and running

Expand Down
3 changes: 2 additions & 1 deletion apps/language_server/lib/language_server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ defmodule ElixirLS.LanguageServer do

children = [
worker(ElixirLS.LanguageServer.Server, [ElixirLS.LanguageServer.Server]),
worker(ElixirLS.LanguageServer.JsonRpc, [[name: ElixirLS.LanguageServer.JsonRpc]])
worker(ElixirLS.LanguageServer.JsonRpc, [[name: ElixirLS.LanguageServer.JsonRpc]]),
worker(ElixirLS.LanguageServer.Providers.WorkspaceSymbols, [[]])
]

opts = [strategy: :one_for_one, name: ElixirLS.LanguageServer.Supervisor, max_restarts: 0]
Expand Down
46 changes: 46 additions & 0 deletions apps/language_server/lib/language_server/erlang_source_file.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
defmodule ElixirLS.LanguageServer.ErlangSourceFile do
def get_beam_file(module, :preloaded) do
case :code.get_object_code(module) do
{_module, _binary, beam_file} -> beam_file
:error -> nil
end
end

def get_beam_file(_module, beam_file), do: beam_file

def beam_file_to_erl_file(beam_file) do
beam_file
|> to_string
|> String.replace(
Regex.recompile!(~r/(.+)\/ebin\/([^\s]+)\.beam$/),
"\\1/src/\\2.erl"
)
end

def module_line(file) do
find_line_by_regex(file, Regex.recompile!(~r/^-module/))
end

def function_line(file, function) do
# TODO use arity?
escaped =
function
|> Atom.to_string()
|> Regex.escape()

find_line_by_regex(file, Regex.recompile!(~r/^#{escaped}\b/))
end

defp find_line_by_regex(file, regex) do
index =
file
|> File.read!()
|> String.split(["\n", "\r\n"])
|> Enum.find_index(&String.match?(&1, regex))

case index do
nil -> nil
i -> i + 1
end
end
end
8 changes: 8 additions & 0 deletions apps/language_server/lib/language_server/protocol.ex
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ defmodule ElixirLS.LanguageServer.Protocol do
end
end

defmacro workspace_symbol_req(id, query) do
quote do
request(unquote(id), "workspace/symbol", %{
"query" => unquote(query)
})
end
end

defmacro signature_help_req(id, uri, line, character) do
quote do
request(unquote(id), "textDocument/signatureHelp", %{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,7 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
https://microsoft.github.io//language-server-protocol/specifications/specification-3-14/#textDocument_documentSymbol
"""

@symbol_enum %{
file: 1,
module: 2,
namespace: 3,
package: 4,
class: 5,
method: 6,
property: 7,
field: 8,
constructor: 9,
enum: 10,
interface: 11,
function: 12,
variable: 13,
constant: 14,
string: 15,
number: 16,
boolean: 17,
array: 18,
object: 19,
key: 20,
null: 21,
enum_member: 22,
struct: 23,
event: 24,
operator: 25,
type_parameter: 26
}
alias ElixirLS.LanguageServer.Providers.SymbolUtils

@defs [:def, :defp, :defmacro, :defmacrop, :defguard, :defguardp, :defdelegate]

Expand Down Expand Up @@ -261,7 +234,7 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
defp build_symbol_information(uri, info) do
%{
name: info.name,
kind: @symbol_enum[info.type],
kind: SymbolUtils.symbol_kind_to_code(info.type),
range: location_to_range(info.location),
selectionRange: location_to_range(info.location),
children: build_symbol_information(uri, info.children)
Expand Down
34 changes: 34 additions & 0 deletions apps/language_server/lib/language_server/providers/symbol_utils.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
defmodule ElixirLS.LanguageServer.Providers.SymbolUtils do
@symbol_enum %{
file: 1,
module: 2,
namespace: 3,
package: 4,
class: 5,
method: 6,
property: 7,
field: 8,
constructor: 9,
enum: 10,
interface: 11,
function: 12,
variable: 13,
constant: 14,
string: 15,
number: 16,
boolean: 17,
array: 18,
object: 19,
key: 20,
null: 21,
enum_member: 22,
struct: 23,
event: 24,
operator: 25,
type_parameter: 26
}

for {kind, code} <- @symbol_enum do
def symbol_kind_to_code(unquote(kind)), do: unquote(code)
end
end
Loading