Skip to content

Commit

Permalink
pass cursor position to binding
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaszsamson committed Jan 10, 2025
1 parent 0e2bdbd commit 706c498
Show file tree
Hide file tree
Showing 16 changed files with 134 additions and 61 deletions.
11 changes: 6 additions & 5 deletions lib/elixir_sense/core/binding.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ defmodule ElixirSense.Core.Binding do
requires: [],
specs: %{},
types: %{},
mods_funs_to_positions: %{}
mods_funs_to_positions: %{},
cursor_position: {1, 1}

def from_env(%State.Env{} = env, %ElixirSense.Core.Metadata{} = metadata) do
def from_env(%State.Env{} = env, %ElixirSense.Core.Metadata{} = metadata, cursor_position) do
%Binding{
vars: env.vars,
attributes: env.attributes,
Expand All @@ -33,7 +34,8 @@ defmodule ElixirSense.Core.Binding do
module: env.module,
function: env.function,
types: metadata.types,
mods_funs_to_positions: metadata.mods_funs_to_positions
mods_funs_to_positions: metadata.mods_funs_to_positions,
cursor_position: cursor_position
}
end

Expand Down Expand Up @@ -99,8 +101,7 @@ defmodule ElixirSense.Core.Binding do
# no variable found - treat as a local call
# this can happen if no parens call is missclassed as variable e.g. by
# Code.Fragment APIs
# TODO pass cursor position
{:local_call, variable, {1, 1}, []}
{:local_call, variable, env.cursor_position, []}

%State.VarInfo{type: type} ->
type
Expand Down
4 changes: 1 addition & 3 deletions lib/elixir_sense/core/type_inference.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,9 @@ defmodule ElixirSense.Core.TypeInference do
end

# remote call
def type_of({{:., _, [target, fun]}, meta, args}, context)
def type_of({{:., _, [target, fun]}, _meta, args}, context)
when is_atom(fun) and is_list(args) do
target = type_of(target, context)
line = Keyword.get(meta, :line, 1)
column = Keyword.get(meta, :column, 1)
{:call, target, fun, Enum.map(args, &type_of(&1, context))}
end

Expand Down
80 changes: 61 additions & 19 deletions lib/elixir_sense/providers/completion/completion_engine.ex
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ defmodule ElixirSense.Providers.Completion.CompletionEngine do
) do
filter = struct_module_filter(only_structs, env, metadata)

case expand_dot_path(path, env, metadata) do
case expand_dot_path(path, env, metadata, cursor_position) do
{:ok, {:atom, mod}} when hint == "" ->
expand_aliases(
mod,
Expand Down Expand Up @@ -284,23 +284,38 @@ defmodule ElixirSense.Providers.Completion.CompletionEngine do
end

# elixir >= 1.14
defp expand_dot_path({:var, ~c"__MODULE__"}, %State.Env{} = env, %Metadata{} = _metadata) do
defp expand_dot_path(
{:var, ~c"__MODULE__"},
%State.Env{} = env,
%Metadata{} = _metadata,
_cursor_position
) do
if env.module != nil and Introspection.elixir_module?(env.module) do
{:ok, {:atom, env.module}}
else
:error
end
end

defp expand_dot_path({:var, var}, %State.Env{} = env, %Metadata{} = metadata) do
value_from_binding({:variable, List.to_atom(var), :any}, env, metadata)
defp expand_dot_path({:var, var}, %State.Env{} = env, %Metadata{} = metadata, cursor_position) do
value_from_binding({:variable, List.to_atom(var), :any}, env, metadata, cursor_position)
end

defp expand_dot_path({:module_attribute, attribute}, %State.Env{} = env, %Metadata{} = metadata) do
value_from_binding({:attribute, List.to_atom(attribute)}, env, metadata)
defp expand_dot_path(
{:module_attribute, attribute},
%State.Env{} = env,
%Metadata{} = metadata,
cursor_position
) do
value_from_binding({:attribute, List.to_atom(attribute)}, env, metadata, cursor_position)
end

defp expand_dot_path({:alias, hint}, %State.Env{} = env, %Metadata{} = metadata) do
defp expand_dot_path(
{:alias, hint},
%State.Env{} = env,
%Metadata{} = metadata,
_cursor_position
) do
alias = hint |> List.to_string() |> String.split(".") |> value_from_alias(env, metadata)

case alias do
Expand All @@ -313,7 +328,8 @@ defmodule ElixirSense.Providers.Completion.CompletionEngine do
defp expand_dot_path(
{:alias, {:local_or_var, var}, hint},
%State.Env{} = env,
%Metadata{} = metadata
%Metadata{} = metadata,
_cursor_position
) do
case var do
~c"__MODULE__" ->
Expand All @@ -333,9 +349,10 @@ defmodule ElixirSense.Providers.Completion.CompletionEngine do
defp expand_dot_path(
{:alias, {:module_attribute, attribute}, hint},
%State.Env{} = env,
%Metadata{} = metadata
%Metadata{} = metadata,
cursor_position
) do
case value_from_binding({:attribute, List.to_atom(attribute)}, env, metadata) do
case value_from_binding({:attribute, List.to_atom(attribute)}, env, metadata, cursor_position) do
{:ok, {:atom, atom}} ->
if Introspection.elixir_module?(atom) do
alias_suffix = hint |> List.to_string() |> String.split(".")
Expand All @@ -354,26 +371,46 @@ defmodule ElixirSense.Providers.Completion.CompletionEngine do
end
end

defp expand_dot_path({:alias, _, _hint}, %State.Env{} = _env, %Metadata{} = _metadata) do
defp expand_dot_path(
{:alias, _, _hint},
%State.Env{} = _env,
%Metadata{} = _metadata,
_cursor_position
) do
:error
end

defp expand_dot_path({:unquoted_atom, var}, %State.Env{} = _env, %Metadata{} = _metadata) do
defp expand_dot_path(
{:unquoted_atom, var},
%State.Env{} = _env,
%Metadata{} = _metadata,
_cursor_position
) do
{:ok, {:atom, List.to_atom(var)}}
end

defp expand_dot_path({:dot, parent, call}, %State.Env{} = env, %Metadata{} = metadata) do
case expand_dot_path(parent, env, metadata) do
defp expand_dot_path(
{:dot, parent, call},
%State.Env{} = env,
%Metadata{} = metadata,
cursor_position
) do
case expand_dot_path(parent, env, metadata, cursor_position) do
{:ok, expanded} ->
value_from_binding({:call, expanded, List.to_atom(call), []}, env, metadata)
value_from_binding(
{:call, expanded, List.to_atom(call), []},
env,
metadata,
cursor_position
)

:error ->
:error
end
end

# elixir >= 1.15
defp expand_dot_path(:expr, %State.Env{} = _env, %Metadata{} = _metadata) do
defp expand_dot_path(:expr, %State.Env{} = _env, %Metadata{} = _metadata, _cursor_position) do
# TODO expand expression
:error
end
Expand Down Expand Up @@ -672,7 +709,7 @@ defmodule ElixirSense.Providers.Completion.CompletionEngine do
cursor_position,
only_structs
) do
case value_from_binding({:attribute, List.to_atom(attribute)}, env, metadata) do
case value_from_binding({:attribute, List.to_atom(attribute)}, env, metadata, cursor_position) do
{:ok, {:atom, atom}} ->
if Introspection.elixir_module?(atom) do
expand_aliases("#{atom}.#{hint}", env, metadata, cursor_position, only_structs, [])
Expand Down Expand Up @@ -1503,9 +1540,14 @@ defmodule ElixirSense.Providers.Completion.CompletionEngine do
end
end

defp value_from_binding(binding_ast, %State.Env{} = env, %Metadata{} = metadata) do
defp value_from_binding(
binding_ast,
%State.Env{} = env,
%Metadata{} = metadata,
cursor_position
) do
case Binding.expand(
Binding.from_env(env, metadata),
Binding.from_env(env, metadata, cursor_position),
binding_ast
) do
:none -> :error
Expand Down
2 changes: 1 addition & 1 deletion lib/elixir_sense/providers/completion/generic_reducer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ defmodule ElixirSense.Providers.Completion.GenericReducer do
module_store: acc.context.module_store
}

case Util.func_call_chain(text_before, env, buffer_metadata) do
case Util.func_call_chain(text_before, env, buffer_metadata, cursor_context.cursor_position) do
[func_call | _] = chain ->
if function_exported?(reducer, :suggestions, 4) do
try do
Expand Down
2 changes: 1 addition & 1 deletion lib/elixir_sense/providers/completion/reducers/params.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ defmodule ElixirSense.Providers.Completion.Reducers.Params do
def add_options(hint, env, buffer_metadata, cursor_context, acc) do
prefix = cursor_context.text_before

binding_env = Binding.from_env(env, buffer_metadata)
binding_env = Binding.from_env(env, buffer_metadata, cursor_context.cursor_position)

%Metadata{mods_funs_to_positions: mods_funs, types: metadata_types} = buffer_metadata

Expand Down
2 changes: 1 addition & 1 deletion lib/elixir_sense/providers/completion/reducers/record.ex
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ defmodule ElixirSense.Providers.Completion.Reducers.Record do
} = metadata,
cursor_position
) do
binding_env = ElixirSense.Core.Binding.from_env(env, metadata)
binding_env = ElixirSense.Core.Binding.from_env(env, metadata, cursor_position)

# check if we are inside local or remote call arguments and parameter is 0, 1 or 2
# record fields can specified on 0, 1 and 2 position in the argument list
Expand Down
7 changes: 4 additions & 3 deletions lib/elixir_sense/providers/completion/reducers/struct.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ defmodule ElixirSense.Providers.Completion.Reducers.Struct do
def add_fields(hint, env, buffer_metadata, context, acc) do
text_before = context.text_before

case find_struct_fields(hint, text_before, env, buffer_metadata) do
case find_struct_fields(hint, text_before, env, buffer_metadata, context.cursor_position) do
{[], _} ->
{:cont, acc}

Expand Down Expand Up @@ -51,9 +51,10 @@ defmodule ElixirSense.Providers.Completion.Reducers.Struct do
module: module,
aliases: aliases
} = env,
%Metadata{} = buffer_metadata
%Metadata{} = buffer_metadata,
cursor_position
) do
binding_env = ElixirSense.Core.Binding.from_env(env, buffer_metadata)
binding_env = ElixirSense.Core.Binding.from_env(env, buffer_metadata, cursor_position)

case Source.which_struct(text_before, module) do
{type, fields_so_far, elixir_prefix, var} ->
Expand Down
10 changes: 8 additions & 2 deletions lib/elixir_sense/providers/completion/reducers/type_specs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ defmodule ElixirSense.Providers.Completion.Reducers.TypeSpecs do
"""

# We only list type specs when inside typespec scope
def add_types(hint, env, file_metadata, %{at_module_body?: _}, acc) do
def add_types(
hint,
env,
file_metadata,
%{at_module_body?: _, cursor_position: cursor_position},
acc
) do
if match?({_, _}, env.typespec) do
%State.Env{
aliases: aliases,
Expand All @@ -35,7 +41,7 @@ defmodule ElixirSense.Providers.Completion.Reducers.TypeSpecs do

%Metadata{mods_funs_to_positions: mods_funs, types: metadata_types} = file_metadata

binding_env = Binding.from_env(env, file_metadata)
binding_env = Binding.from_env(env, file_metadata, cursor_position)

{mod, hint} =
hint
Expand Down
2 changes: 1 addition & 1 deletion lib/elixir_sense/providers/definition/locator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ defmodule ElixirSense.Providers.Definition.Locator do
} = env,
metadata
) do
binding_env = Binding.from_env(env, metadata)
binding_env = Binding.from_env(env, metadata, context.begin)

type = SurroundContext.to_binding(context.context, module)

Expand Down
2 changes: 1 addition & 1 deletion lib/elixir_sense/providers/hover/docs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ defmodule ElixirSense.Providers.Hover.Docs do
} = env,
metadata
) do
binding_env = Binding.from_env(env, metadata)
binding_env = Binding.from_env(env, metadata, context.begin)

type = SurroundContext.to_binding(context.context, module)

Expand Down
2 changes: 1 addition & 1 deletion lib/elixir_sense/providers/implementation/locator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ defmodule ElixirSense.Providers.Implementation.Locator do
} = env,
metadata
) do
binding_env = Binding.from_env(env, metadata)
binding_env = Binding.from_env(env, metadata, context.begin)

type = SurroundContext.to_binding(context.context, module)

Expand Down
29 changes: 22 additions & 7 deletions lib/elixir_sense/providers/plugins/ecto/query.ex
Original file line number Diff line number Diff line change
Expand Up @@ -193,30 +193,45 @@ defmodule ElixirSense.Providers.Plugins.Ecto.Query do
}
end

defp infer_type({:__aliases__, _, [{:__MODULE__, _, _} | list]}, _vars, env, buffer_metadata) do
defp infer_type(
{:__aliases__, _, [{:__MODULE__, _, _} | list]},
_vars,
env,
buffer_metadata,
cursor_position
) do
mod = Module.concat(env.module, Module.concat(list))
{actual_mod, _, _, _} = Util.actual_mod_fun({mod, nil}, false, env, buffer_metadata)

{actual_mod, _, _, _} =
Util.actual_mod_fun({mod, nil}, false, env, buffer_metadata, cursor_position)

actual_mod
end

defp infer_type({:__aliases__, _, list}, _vars, env, buffer_metadata) do
defp infer_type({:__aliases__, _, list}, _vars, env, buffer_metadata, cursor_position) do
mod = Module.concat(list)

{actual_mod, _, _, _} =
Util.actual_mod_fun({mod, nil}, hd(list) == Elixir, env, buffer_metadata)
Util.actual_mod_fun({mod, nil}, hd(list) == Elixir, env, buffer_metadata, cursor_position)

actual_mod
end

defp infer_type({:assoc, _, [{var, _, _}, assoc]}, vars, _env, _buffer_metadata) do
defp infer_type(
{:assoc, _, [{var, _, _}, assoc]},
vars,
_env,
_buffer_metadata,
_cursor_position
) do
var_type = vars[to_string(var)][:type]

if var_type && function_exported?(var_type, :__schema__, 2) do
var_type.__schema__(:association, assoc).related
end
end

defp infer_type(_, _vars, _env, _buffer_metadata) do
defp infer_type(_, _vars, _env, _buffer_metadata, _cursor_position) do
nil
end

Expand All @@ -240,7 +255,7 @@ defmodule ElixirSense.Providers.Plugins.Ecto.Query do
Enum.reduce(matches, %{}, fn [_, _, var, expr], bindings ->
case Code.string_to_quoted(expr) do
{:ok, expr_ast} ->
type = infer_type(expr_ast, bindings, env, buffer_metadata)
type = infer_type(expr_ast, bindings, env, buffer_metadata, {line, col})
Map.put(bindings, var, %{type: type})

_ ->
Expand Down
8 changes: 6 additions & 2 deletions lib/elixir_sense/providers/plugins/phoenix.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ defmodule ElixirSense.Providers.Plugins.Phoenix do
@impl true
def suggestions(hint, {Phoenix.Router, func, 1, _info}, _list, opts)
when func in @phoenix_route_funcs do
binding = Binding.from_env(opts.env, opts.buffer_metadata)
binding =
Binding.from_env(opts.env, opts.buffer_metadata, opts.cursor_context.cursor_position)

{_, scope_alias} = Scope.within_scope(opts.cursor_context.text_before, binding)

case find_controllers(opts.module_store, opts.env, hint, scope_alias) do
Expand All @@ -44,7 +46,9 @@ defmodule ElixirSense.Providers.Plugins.Phoenix do
opts
)
when func in @phoenix_route_funcs do
binding_env = Binding.from_env(opts.env, opts.buffer_metadata)
binding_env =
Binding.from_env(opts.env, opts.buffer_metadata, opts.cursor_context.cursor_position)

{_, scope_alias} = Scope.within_scope(opts.cursor_context.text_before)
{module, _} = Source.get_mod([module], binding_env)

Expand Down
Loading

0 comments on commit 706c498

Please sign in to comment.