Skip to content

Commit

Permalink
Migrate to Code.Fragment.surround_context
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaszsamson committed Nov 12, 2022
1 parent 1bab571 commit e5d3d9a
Show file tree
Hide file tree
Showing 12 changed files with 362 additions and 636 deletions.
77 changes: 40 additions & 37 deletions lib/elixir_sense.ex
Original file line number Diff line number Diff line change
Expand Up @@ -50,29 +50,37 @@ defmodule ElixirSense do
iex> types |> String.split("\n") |> Enum.at(4)
"@type default :: any"
"""
@spec docs(String.t(), pos_integer, pos_integer) :: %{
subject: String.t(),
actual_subject: String.t(),
docs: Introspection.docs()
}
@spec docs(String.t(), pos_integer, pos_integer) ::
%{
actual_subject: String.t(),
docs: Introspection.docs(),
range: %{
begin: {pos_integer, pos_integer},
end: {pos_integer, pos_integer}
}
}
| nil
def docs(code, line, column) do
case Source.subject(code, line, column) do
nil ->
%{
subject: "",
actual_subject: "",
docs: %{docs: "No documentation available\n", types: ""}
}
case Code.Fragment.surround_context(code, {line, column}) do
:none ->
nil

subject ->
%{begin: begin_pos, end: end_pos, context: context} ->
metadata = Parser.parse_string(code, true, true, line)

env = Metadata.get_env(metadata, line)

{actual_subject, docs} =
Docs.all(subject, env, metadata.mods_funs_to_positions, metadata.types)
Docs.all(context, env, metadata.mods_funs_to_positions, metadata.types)

%{subject: subject, actual_subject: actual_subject, docs: docs}
%{
actual_subject: actual_subject,
docs: docs,
range: %{
begin: begin_pos,
end: end_pos
}
}
end
end

Expand All @@ -93,27 +101,19 @@ defmodule ElixirSense do
"""
@spec definition(String.t(), pos_integer, pos_integer) :: Location.t() | nil
def definition(code, line, column) do
case Source.subject(code, line, column) do
nil ->
case Code.Fragment.surround_context(code, {line, column}) do
:none ->
nil

subject ->
%{context: context} ->
buffer_file_metadata = Parser.parse_string(code, true, true, line)

env = Metadata.get_env(buffer_file_metadata, line)

calls =
buffer_file_metadata.calls[line]
|> List.wrap()
|> Enum.filter(fn %State.CallInfo{position: {_call_line, call_column}} ->
call_column <= column
end)

Definition.find(
subject,
context,
env,
buffer_file_metadata.mods_funs_to_positions,
calls,
buffer_file_metadata.types
)
end
Expand All @@ -133,17 +133,17 @@ defmodule ElixirSense do
"""
@spec implementations(String.t(), pos_integer, pos_integer) :: [Location.t()]
def implementations(code, line, column) do
case Source.subject(code, line, column) do
nil ->
case Code.Fragment.surround_context(code, {line, column}) do
:none ->
[]

subject ->
%{context: context} ->
buffer_file_metadata = Parser.parse_string(code, true, true, line)

env = Metadata.get_env(buffer_file_metadata, line)

Implementation.find(
subject,
context,
env,
buffer_file_metadata.mods_funs_to_positions,
buffer_file_metadata.types
Expand Down Expand Up @@ -404,8 +404,14 @@ defmodule ElixirSense do
call_trace_t()
) :: [References.reference_info()]
def references(code, line, column, trace) do
case Source.subject_with_position(code, line, column) do
{subject, {line, col}} ->
case Code.Fragment.surround_context(code, {line, column}) do
:none ->
[]

%{
context: context,
end: {line, col}
} ->
buffer_file_metadata = Parser.parse_string(code, true, true, line)

env =
Expand All @@ -426,17 +432,14 @@ defmodule ElixirSense do
arity = Metadata.get_call_arity(buffer_file_metadata, line, col)

References.find(
subject,
context,
arity,
env,
vars,
attributes,
buffer_file_metadata,
trace
)

_ ->
[]
end
end

Expand Down
4 changes: 3 additions & 1 deletion lib/elixir_sense/core/metadata.ex
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ defmodule ElixirSense.Core.Metadata do
def get_call_arity(%__MODULE__{} = metadata, line, col) do
calls = get_calls(metadata, line)

case Enum.find(calls, fn %State.CallInfo{position: {_line, column}} -> column == col end) do
case Enum.find(calls, fn %State.CallInfo{position: {_line, column}, func: func} ->
column + String.length(Atom.to_string(func)) == col
end) do
%{arity: arity} -> arity
_ -> nil
end
Expand Down
107 changes: 0 additions & 107 deletions lib/elixir_sense/core/source.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,6 @@ defmodule ElixirSense.Core.Source do

@line_break ["\n", "\r\n", "\r"]
@empty_graphemes [" ", "\t"] ++ @line_break
@stop_graphemes [
"{",
"}",
"(",
")",
"[",
"]",
"<",
">",
"+",
"-",
"*",
"&",
"^",
",",
";",
"~",
"%",
"=",
"\\",
"\/",
"$",
"!",
"?",
"`",
"#"
] ++ @empty_graphemes

@spec split_module_and_hint(String.t(), module | nil, [{module, module}]) ::
{nil | module | {:attribute, atom}, String.t()}
Expand Down Expand Up @@ -161,50 +134,6 @@ defmodule ElixirSense.Core.Source do
rest
end

@spec subject(String.t(), pos_integer, pos_integer) :: nil | String.t()
def subject(code, line, col) do
acc = %{line: line, col: col, pos_found: false, candidate: [], pos: nil}

code =
code
|> split_lines
|> Enum.map_join("\n", fn line ->
# this is a naive comment strip - it will not honour # in strings, chars etc
Regex.replace(~r/(?<!\<)\#(?!\{).*$/, line, "")
end)

case walk_text(code, acc, &find_subject/5) do
%{candidate: []} ->
nil

%{candidate: candidate} ->
candidate |> Enum.reverse() |> Enum.join()
end
end

@spec subject_with_position(String.t(), pos_integer, pos_integer) ::
nil | {String.t(), {pos_integer, pos_integer}}
def subject_with_position(code, line, col) do
acc = %{line: line, col: col, pos_found: false, candidate: [], pos: nil}

case walk_text(code, acc, &find_subject/5) do
%{candidate: []} ->
nil

%{candidate: candidate, pos: {line, col}} ->
subject = candidate |> Enum.reverse() |> Enum.join()

last_part =
subject
|> String.reverse()
|> String.split(".", parts: 2)
|> Enum.at(0)
|> String.reverse()

{subject, {line, col - String.length(last_part) + 1}}
end
end

@doc ~S"""
Finds next word offset
Expand Down Expand Up @@ -394,42 +323,6 @@ defmodule ElixirSense.Core.Source do
{rest, %{acc | buffer: [grapheme | buffer]}}
end

defp find_subject(grapheme, rest, line, col, %{pos_found: false, line: line, col: col} = acc) do
find_subject(grapheme, rest, line, col, %{acc | pos_found: true})
end

defp find_subject("." = grapheme, rest, _line, _col, %{pos_found: false} = acc) do
{rest, %{acc | candidate: [grapheme | acc.candidate]}}
end

defp find_subject(".", _rest, line, col, %{pos_found: true} = acc) do
{"", %{acc | pos: {line, col - 1}}}
end

defp find_subject(grapheme, rest, _line, _col, %{candidate: [_ | _]} = acc)
when grapheme in ["!", "?"] do
{rest, %{acc | candidate: [grapheme | acc.candidate]}}
end

defp find_subject(grapheme, rest, _line, _col, %{candidate: ["." | _]} = acc)
when grapheme in @stop_graphemes do
{rest, acc}
end

defp find_subject(grapheme, rest, _line, _col, %{pos_found: false} = acc)
when grapheme in @stop_graphemes do
{rest, %{acc | candidate: []}}
end

defp find_subject(grapheme, _rest, line, col, %{pos_found: true} = acc)
when grapheme in @stop_graphemes do
{"", %{acc | pos: {line, col - 1}}}
end

defp find_subject(grapheme, rest, _line, _col, acc) do
{rest, %{acc | candidate: [grapheme | acc.candidate]}}
end

defp do_walk_text(text, func, line, col, acc) do
case String.next_grapheme(text) do
nil ->
Expand Down
98 changes: 98 additions & 0 deletions lib/elixir_sense/core/surround_context.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
defmodule ElixirSense.Core.SurroundContext do
@moduledoc false
alias ElixirSense.Core.Introspection

# TODO structs from 1.14
def to_binding({:alias, charlist}, _current_module) do
{{:atom, :"Elixir.#{charlist}"}, nil}
end

def to_binding({:dot, inside_dot, charlist}, current_module) do
{inside_dot_to_binding(inside_dot, current_module), :"#{charlist}"}
end

def to_binding({:local_or_var, '__MODULE__'}, current_module) do
if current_module not in [nil, Elixir] do
{:atom, current_module}
end
end

def to_binding({:local_or_var, charlist}, _current_module) do
{:variable, :"#{charlist}"}
end

def to_binding({:local_arity, charlist}, _current_module) do
{nil, :"#{charlist}"}
end

def to_binding({:local_call, charlist}, _current_module) do
{nil, :"#{charlist}"}
end

def to_binding({:module_attribute, charlist}, _current_module) do
{:attribute, :"#{charlist}"}
end

def to_binding({:operator, charlist}, _current_module) do
{nil, :"#{charlist}"}
end

def to_binding({:unquoted_atom, charlist}, _current_module) do
{{:atom, :"#{charlist}"}, nil}
end

def to_binding({:sigil, charlist}, _current_module) do
{nil, :"sigil_#{charlist}"}
end

def to_binding({:struct, charlist}, _current_module) when is_list(charlist) do
{{:atom, :"Elixir.#{charlist}"}, nil}
end

defp inside_dot_to_binding({:module_attribute, inside_charlist}, _current_module) do
{:attribute, :"#{inside_charlist}"}
end

defp inside_dot_to_binding({:var, inside_charlist}, _current_module) do
{:variable, :"#{inside_charlist}"}
end

defp inside_dot_to_binding({:unquoted_atom, inside_charlist}, _current_module) do
{:atom, :"#{inside_charlist}"}
end

defp inside_dot_to_binding({:alias, inside_charlist}, _current_module)
when is_list(inside_charlist) do
{:atom, :"Elixir.#{inside_charlist}"}
end

defp inside_dot_to_binding(
{:alias, {:local_or_var, '__MODULE__'}, inside_charlist},
current_module
) do
if current_module not in [nil, Elixir] do
{:atom, :"#{current_module |> Atom.to_string()}.#{inside_charlist}"}
end
end

# TODO attr?
defp inside_dot_to_binding({:alias, _other, _inside_charlist}, _current_module) do
nil
end

defp inside_dot_to_binding({:dot, inside_dot, inside_charlist}, current_module) do
{:call, inside_dot_to_binding(inside_dot, current_module), :"#{inside_charlist}", []}
end

def expand({{:atom, module}, func}, aliases) do
{Introspection.expand_alias(module, aliases), func}
end

def expand({nil, func}, _aliases) do
{nil, func}
end

def expand({:none, func}, _aliases) do
{nil, func}
end
end
Loading

0 comments on commit e5d3d9a

Please sign in to comment.