-
-
Notifications
You must be signed in to change notification settings - Fork 83
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Document Symbols support Added document symbols, which supports the following symbols: * Modules * Functions, both private and public * Typespecs * Module Attributes * ExUnit describe / setup / tests Fixes #382 * Added support for block ranges and detail ranges For document symbols, we need to provide support for block ranges for things like modules, functions and tests, so that the editor can understand if it's inside the given symbol. The LSP also would like to have selection ranges, which are more specific, and would, say highlight the function definition. * Upgraded sourceror Sourceror had a bug calculating end lines, which was causing responses not to be emitted, but only when unicode was present. It was emitting the ending several characters beyond where the `end` keyword was, and this would fail during conversion as being out of bounds.
- Loading branch information
Showing
20 changed files
with
1,072 additions
and
21 deletions.
There are no files selected for viewing
15 changes: 15 additions & 0 deletions
15
apps/protocol/lib/generated/lexical/protocol/types/document/symbol.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# This file's contents are auto-generated. Do not edit. | ||
defmodule Lexical.Protocol.Types.Document.Symbol do | ||
alias Lexical.Proto | ||
alias Lexical.Protocol.Types | ||
use Proto | ||
|
||
deftype children: optional(list_of(Types.Document.Symbol)), | ||
deprecated: optional(boolean()), | ||
detail: optional(string()), | ||
kind: Types.Symbol.Kind, | ||
name: string(), | ||
range: Types.Range, | ||
selection_range: Types.Range, | ||
tags: optional(list_of(Types.Symbol.Tag)) | ||
end |
10 changes: 10 additions & 0 deletions
10
apps/protocol/lib/generated/lexical/protocol/types/document/symbol/params.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# This file's contents are auto-generated. Do not edit. | ||
defmodule Lexical.Protocol.Types.Document.Symbol.Params do | ||
alias Lexical.Proto | ||
alias Lexical.Protocol.Types | ||
use Proto | ||
|
||
deftype partial_result_token: optional(Types.Progress.Token), | ||
text_document: Types.TextDocument.Identifier, | ||
work_done_token: optional(Types.Progress.Token) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
apps/remote_control/lib/lexical/remote_control/code_intelligence/symbols.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
defmodule Lexical.RemoteControl.CodeIntelligence.Symbols do | ||
alias Lexical.Document | ||
alias Lexical.RemoteControl.CodeIntelligence.Symbols | ||
alias Lexical.RemoteControl.Search.Indexer | ||
alias Lexical.RemoteControl.Search.Indexer.Entry | ||
alias Lexical.RemoteControl.Search.Indexer.Extractors | ||
|
||
@block_types [ | ||
:ex_unit_describe, | ||
:ex_unit_setup, | ||
:ex_unit_setup_all, | ||
:ex_unit_test, | ||
:module, | ||
:private_function, | ||
:public_function | ||
] | ||
|
||
@symbol_extractors [ | ||
Extractors.FunctionDefinition, | ||
Extractors.Module, | ||
Extractors.ModuleAttribute, | ||
Extractors.StructDefinition, | ||
Extractors.ExUnit | ||
] | ||
|
||
def for_document(%Document{} = document) do | ||
{:ok, entries} = Indexer.Source.index_document(document, @symbol_extractors) | ||
|
||
definitions = Enum.filter(entries, &(&1.subtype == :definition)) | ||
to_symbols(document, definitions) | ||
end | ||
|
||
defp to_symbols(%Document{} = document, entries) do | ||
entries_by_block_id = Enum.group_by(entries, & &1.block_id) | ||
rebuild_structure(entries_by_block_id, document, :root) | ||
end | ||
|
||
defp rebuild_structure(entries_by_block_id, %Document{} = document, block_id) do | ||
block_entries = Map.get(entries_by_block_id, block_id, []) | ||
|
||
Enum.flat_map(block_entries, fn | ||
%Entry{type: type, subtype: :definition} = entry when type in @block_types -> | ||
result = | ||
if Map.has_key?(entries_by_block_id, entry.id) do | ||
children = | ||
entries_by_block_id | ||
|> rebuild_structure(document, entry.id) | ||
|> Enum.sort_by(fn %Symbols.Document{} = symbol -> | ||
start = symbol.range.start | ||
{start.line, start.character} | ||
end) | ||
|
||
Symbols.Document.from(document, entry, children) | ||
else | ||
Symbols.Document.from(document, entry) | ||
end | ||
|
||
case result do | ||
{:ok, symbol} -> [symbol] | ||
_ -> [] | ||
end | ||
|
||
%Entry{} = entry -> | ||
case Symbols.Document.from(document, entry) do | ||
{:ok, symbol} -> [symbol] | ||
_ -> [] | ||
end | ||
end) | ||
end | ||
end |
89 changes: 89 additions & 0 deletions
89
apps/remote_control/lib/lexical/remote_control/code_intelligence/symbols/document.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
defmodule Lexical.RemoteControl.CodeIntelligence.Symbols.Document do | ||
alias Lexical.Document | ||
alias Lexical.Formats | ||
alias Lexical.RemoteControl.Search.Indexer.Entry | ||
|
||
defstruct [:name, :type, :range, :detail_range, :detail, children: []] | ||
|
||
def from(%Document{} = document, %Entry{} = entry, children \\ []) do | ||
case name_and_type(entry.type, entry, document) do | ||
{name, type} -> | ||
range = entry.block_range || entry.range | ||
|
||
{:ok, | ||
%__MODULE__{ | ||
name: name, | ||
type: type, | ||
range: range, | ||
detail_range: entry.range, | ||
children: children | ||
}} | ||
|
||
_ -> | ||
:error | ||
end | ||
end | ||
|
||
@def_regex ~r/def\w*\s+/ | ||
@do_regex ~r/\s*do\s*$/ | ||
|
||
defp name_and_type(function, %Entry{} = entry, %Document{} = document) | ||
when function in [:public_function, :private_function] do | ||
fragment = Document.fragment(document, entry.range.start, entry.range.end) | ||
|
||
name = | ||
fragment | ||
|> String.replace(@def_regex, "") | ||
|> String.replace(@do_regex, "") | ||
|
||
{name, function} | ||
end | ||
|
||
@ignored_attributes ~w[spec doc moduledoc derive impl tag] | ||
@type_name_regex ~r/@type\s+[^\s]+/ | ||
|
||
defp name_and_type(:module_attribute, %Entry{} = entry, document) do | ||
case String.split(entry.subject, "@") do | ||
[_, name] when name in @ignored_attributes -> | ||
nil | ||
|
||
[_, "type"] -> | ||
type_text = Document.fragment(document, entry.range.start, entry.range.end) | ||
|
||
name = | ||
case Regex.scan(@type_name_regex, type_text) do | ||
[[match]] -> match | ||
_ -> "@type ??" | ||
end | ||
|
||
{name, :type} | ||
|
||
[_, name] -> | ||
{"@#{name}", :module_attribute} | ||
end | ||
end | ||
|
||
defp name_and_type(ex_unit, %Entry{} = entry, document) | ||
when ex_unit in [:ex_unit_describe, :ex_unit_setup, :ex_unit_test] do | ||
name = | ||
document | ||
|> Document.fragment(entry.range.start, entry.range.end) | ||
|> String.trim() | ||
|> String.replace(@do_regex, "") | ||
|
||
{name, ex_unit} | ||
end | ||
|
||
defp name_and_type(:struct, %Entry{} = entry, _document) do | ||
module_name = Formats.module(entry.subject) | ||
{"%#{module_name}{}", :struct} | ||
end | ||
|
||
defp name_and_type(type, %Entry{subject: name}, _document) when is_atom(name) do | ||
{Formats.module(name), type} | ||
end | ||
|
||
defp name_and_type(type, %Entry{} = entry, _document) do | ||
{to_string(entry.subject), type} | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.