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 a signature provider #477

Closed
josevalim opened this issue Jul 28, 2021 · 1 comment · Fixed by #640
Closed

Add a signature provider #477

josevalim opened this issue Jul 28, 2021 · 1 comment · Fixed by #640
Labels
feature New feature or request

Comments

@josevalim
Copy link
Contributor

This requires some advanced features in Elixir's Code.Fragment, so we are keeping it here for now to track it.

@josevalim josevalim added the feature New feature or request label Jul 28, 2021
@josevalim
Copy link
Contributor Author

josevalim commented Oct 16, 2021

So the bulk of the work necessary to implement this feature is available via the new Code.Fragment.container_cursor_to_quoted/2. Unfortunately this function requires changes to the tokenizer, so we can only try it out once Elixir v1.13.0-rc is out (or using master).

I drop a proof of concept on how to implement this feature. For Livebook, we want to find the closest node to a cursor that is not: an operator nor a container. We also want to reject any call where the cursor is directly under a do-end block. The code is below. (Edit: the code below does not expand the pipe operator, you need to do so while traversing).

Also note that we can use this functionality to implement struct field autocompletion, as done in IEx: elixir-lang/elixir@25614e0


with {:ok, ast} <- Code.Fragment.container_cursor_to_quoted(cursor, token_metadata: true),
     {left, meta, right} <- find_cursor_call(ast, :root),
     false <- cursor_in_do_end?(meta, right) do
  # We are almost there. left may be a local/import or a
  # remote call. In case of a remote call, it may be a
  # dynamic call (such as a var), an alias that does not
  # exist, etc. So those cases have to be validated too.
  {left, meta, right}
else
  _ -> nil
end

defp find_cursor_call({:__cursor__, _, []}, acc) do
  acc
end

defp find_cursor_call({left, meta, right} = node, acc) when is_list(right) do
  if is_atom(left) and (Macro.operator?(left, length(right))) or
      left in [:__block__, :__aliases__, :., :{}, :<<>>, :%, :%{}]) do
    find_cursor_call(left, acc) || find_cursor_call(right, acc)
  else
    find_cursor_call(left, node) || find_cursor_call(right, node)
  end
end

defp find_cursor_call({left, meta, right}, acc) do
  find_cursor_call(left, acc) || find_cursor_call(right, acc)
end

defp find_cursor_call({left, right}, acc) do
  find_cursor_call(left, acc) || find_cursor_call(right, acc)
end

defp find_cursor_call(list, acc) when is_list(list) do
  Enum.find_value(list, &find_cursor_call(&1, acc))
end

defp find_cursor_call(_other, _acc) do
  nil
end

defp cursor_in_do_end?(meta, args) do
  is_list(meta[:do]) and
    Enum.any?(Macro.prewalker(List.last(args)), &match?({:__cursor__, _, []}, &1))
end

@josevalim josevalim added this to the v0.4 milestone Oct 20, 2021
@jonatanklosko jonatanklosko modified the milestones: v0.3.2, v0.3.3 Nov 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants