Skip to content

Commit

Permalink
Update elixir sense.
Browse files Browse the repository at this point in the history
  • Loading branch information
edelvalle committed Sep 23, 2017
1 parent 5d871ee commit 79a30e9
Show file tree
Hide file tree
Showing 25 changed files with 332 additions and 185 deletions.
15 changes: 13 additions & 2 deletions elixir_sense/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,17 @@ For coverage:
mix coveralls
```

## WIP
## Credits

Credits, License, ...
- This project probably wouldn't even exist without all the work done by Samuel Tonini and all contributors from [alchemist-server](https://github.com/tonini/alchemist-server).
- The Expand feature was inspired by the [mex](https://github.com/mrluc/mex) tool by Luc Fueston. There's also a very nice post where he describes the whole process of [Building A Macro-Expansion Helper for IEx](http://blog.maketogether.com/building-a-macro-expansion-helper/).

## License (The MIT License)

Copyright (c) 2017 Marlus Saraiva

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
70 changes: 41 additions & 29 deletions elixir_sense/lib/alchemist/helpers/complete.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ defmodule Alchemist.Helpers.Complete do

def run(exp) do
code = case is_bitstring(exp) do
true -> exp |> String.to_char_list
true -> exp |> String.to_charlist
_ -> exp
end

{status, result, list } = expand(code |> Enum.reverse)
{status, result, list} = expand(code |> Enum.reverse)

case { status, result, list } do
{ :no, _, _ } -> ''
{ :yes, [], _ } -> List.insert_at(list, 0, %{type: :hint, value: "#{exp}"})
{ :yes, _, [] } -> run(code ++ result)
{ :yes, _, _ } -> List.insert_at(run(code ++ result), 1, Enum.at(list, 0))
case {status, result, list} do
{:no, _, _} -> ''
{:yes, [], _} -> List.insert_at(list, 0, %{type: :hint, value: "#{exp}"})
{:yes, _, []} -> run(code ++ result)
{:yes, _, _} -> List.insert_at(run(code ++ result), 1, Enum.at(list, 0))
#
end
end
Expand All @@ -40,9 +40,9 @@ defmodule Alchemist.Helpers.Complete do
!({f, a} in @builtin_functions) and (function_exported?(mod, f, a) or macro_exported?(mod, f, a))
end
accept_function = fn
(mod, mod, _, _, _ ) -> true
(mod, mod, _, _, _) -> true
(_ , _ , _, _, :undefined) -> false
(_ , mod, f, a, _ ) -> exported?.(mod, f, a)
(_ , mod, f, a, _) -> exported?.(mod, f, a)
end

for module <- modules, module != Elixir do
Expand Down Expand Up @@ -73,9 +73,9 @@ defmodule Alchemist.Helpers.Complete do
expand_import("")
end

def expand([h|t]=expr) do
def expand([h|t] = expr) do
cond do
h === ?. and t != []->
h === ?. and t != [] ->
expand_dot(reduce(t))
h === ?: ->
expand_erlang_modules()
Expand Down Expand Up @@ -131,7 +131,7 @@ defmodule Alchemist.Helpers.Complete do
end

defp yes(hint, entries) do
{:yes, String.to_char_list(hint), entries}
{:yes, String.to_charlist(hint), entries}
end

defp no do
Expand All @@ -151,14 +151,14 @@ defmodule Alchemist.Helpers.Complete do
end
end

defp format_expansion([first|_]=entries, hint) do
defp format_expansion([first|_] = entries, hint) do
binary = Enum.map(entries, &(&1.name))
length = byte_size(hint)
prefix = :binary.longest_common_prefix(binary)
if prefix in [0, length] do
yes("", Enum.flat_map(entries, &to_entries/1))
else
yes(:binary.part(first.name, prefix, length-prefix), [])
yes(:binary.part(first.name, prefix, length - prefix), [])
end
end

Expand All @@ -171,8 +171,9 @@ defmodule Alchemist.Helpers.Complete do

# Elixir.fun
defp expand_call({:__aliases__, _, list}, hint) do
expand_alias(list)
|> normalize_module
list
|> expand_alias()
|> normalize_module()
|> expand_require(hint)
end

Expand Down Expand Up @@ -210,8 +211,9 @@ defmodule Alchemist.Helpers.Complete do
end

defp expand_elixir_modules(list, hint) do
expand_alias(list)
|> normalize_module
list
|> expand_alias()
|> normalize_module()
|> expand_elixir_modules(hint, [])
end

Expand All @@ -236,8 +238,9 @@ defmodule Alchemist.Helpers.Complete do
end
end

defp env_aliases() do
Application.get_env(:"alchemist.el", :aliases)
defp env_aliases do
:"alchemist.el"
|> Application.get_env(:aliases)
|> format_aliases
end

Expand All @@ -263,7 +266,8 @@ defmodule Alchemist.Helpers.Complete do
mod_as_atom = mod |> String.to_atom
desc = Introspection.get_module_docs_summary(mod_as_atom)
subtype = Introspection.get_module_subtype(mod_as_atom)
%{kind: :module, type: :elixir, name: Enum.at(parts, depth-1), desc: desc, subtype: subtype}
%{kind: :module, type: :elixir, name: Enum.at(parts, depth - 1),
desc: desc, subtype: subtype}
end
|> Enum.uniq_by(fn %{name: name} -> name end)
end
Expand All @@ -279,7 +283,8 @@ defmodule Alchemist.Helpers.Complete do
end

defp match_modules(hint, root) do
get_modules(root)
root
|> get_modules()
|> :lists.usort()
|> Enum.drop_while(& not starts_with?(&1, hint))
|> Enum.take_while(& starts_with?(&1, hint))
Expand Down Expand Up @@ -321,15 +326,17 @@ defmodule Alchemist.Helpers.Complete do

list = Enum.reduce falist, [], fn {f, a, func_kind, doc, spec}, acc ->
case :lists.keyfind(f, 1, acc) do
{f, aa, func_kind, docs, specs} -> :lists.keyreplace(f, 1, acc, {f, [a|aa], func_kind, [doc|docs], [spec|specs]})
{f, aa, func_kind, docs, specs} ->
:lists.keyreplace(f, 1, acc, {f, [a|aa], func_kind, [doc|docs], [spec|specs]})
false -> [{f, [a], func_kind, [doc], [spec]}|acc]
end
end

for {fun, arities, func_kind, docs, specs} <- list,
name = Atom.to_string(fun),
starts_with?(name, hint) do
%{kind: :function, name: name, arities: arities, module: mod, func_kind: func_kind, docs: docs, specs: specs}
%{kind: :function, name: name, arities: arities, module: mod,
func_kind: func_kind, docs: docs, specs: specs}
end |> :lists.sort()

_otherwise -> []
Expand All @@ -341,15 +348,19 @@ defmodule Alchemist.Helpers.Complete do
funs = if docs = Code.get_docs(mod, :docs) do
specs = Introspection.get_module_specs(mod)
for {{f, a}, _line, func_kind, _sign, doc} = func_doc <- docs, doc != false do
spec = Map.get(specs, {f,a}, "")
spec = Map.get(specs, {f, a}, "")
{f, a, func_kind, func_doc, spec}
end
else
macros = mod.__info__(:macros) |> Enum.map(fn {f, a} -> {f, a, :macro, nil, nil} end)
functions = mod.__info__(:functions) |> Enum.map(fn {f, a} -> {f, a, :function, nil, nil} end)
macros = :macros
|> mod.__info__()
|> Enum.map(fn {f, a} -> {f, a, :macro, nil, nil} end)
functions = :functions
|> mod.__info__()
|> Enum.map(fn {f, a} -> {f, a, :function, nil, nil} end)
macros ++ functions
end
funs ++ (@builtin_functions |> Enum.map(fn {f,a} -> {f, a, :function, nil, nil } end))
funs ++ (@builtin_functions |> Enum.map(fn {f, a} -> {f, a, :function, nil, nil} end))
else
for {f, a} <- mod.module_info(:exports) do
case f |> Atom.to_string do
Expand Down Expand Up @@ -386,7 +397,8 @@ defmodule Alchemist.Helpers.Complete do
:defmacro -> "macro"
_ -> "function"
end
mod_name = mod |> Introspection.module_to_string
mod_name = mod
|> Introspection.module_to_string
%{type: kind, name: name, arity: a, args: fun_args, origin: mod_name, summary: desc, spec: spec}
end
end
Expand Down
16 changes: 11 additions & 5 deletions elixir_sense/lib/alchemist/helpers/module_info.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ defmodule Alchemist.Helpers.ModuleInfo do

def expand_alias([name | rest] = list, aliases) do
module = Module.concat(Elixir, name)
Enum.find_value(aliases, list, fn {alias, mod} ->
aliases
|> Enum.find_value(list, fn {alias, mod} ->
if alias === module do
case Atom.to_string(mod) do
"Elixir." <> mod ->
Expand All @@ -25,7 +26,8 @@ defmodule Alchemist.Helpers.ModuleInfo do
mod
end
end
end) |> normalize_module
end)
|> normalize_module
end

def get_functions(module, hint) do
Expand All @@ -39,8 +41,9 @@ defmodule Alchemist.Helpers.ModuleInfo do
false -> [{f, [a]}|acc]
end
end

do_get_functions(list, hint) |> :lists.sort()
list
|> do_get_functions(hint)
|> :lists.sort()
end

def has_function?(module, function) do
Expand All @@ -58,7 +61,10 @@ defmodule Alchemist.Helpers.ModuleInfo do
defp get_module_funs(module) do
case Code.ensure_loaded(module) do
{:module, _} ->
(module.module_info(:functions) |> filter_module_funs) ++ module.__info__(:macros)
(:functions
|> module.module_info()
|> filter_module_funs)
++ module.__info__(:macros)
_otherwise ->
[]
end
Expand Down
6 changes: 3 additions & 3 deletions elixir_sense/lib/elixir_sense.ex
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ defmodule ElixirSense do
...> '''
iex> {path, line} = ElixirSense.definition(code, 3, 11)
iex> "#{Path.basename(path)}:#{to_string(line)}"
"enum.ex:2523"
"enum.ex:2576"
"""
@spec definition(String.t, pos_integer, pos_integer) :: Definition.location
def definition(code, line, column) do
Expand All @@ -87,10 +87,10 @@ defmodule ElixirSense do
[":application", ":application_controller", ":application_master", ":application_starter"]
iex> ElixirSense.all_modules() |> Enum.take(-4)
["Version.Parser", "Version.Parser.DSL", "Version.Requirement", "WithClauseError"]
["Version.InvalidVersionError", "Version.Parser", "Version.Requirement", "WithClauseError"]
"""
def all_modules() do
def all_modules do
Introspection.all_modules()
|> Enum.map(&Atom.to_string(&1))
|> Enum.map(fn x -> if String.downcase(x) == x do ":" <> x else x end end)
Expand Down
55 changes: 27 additions & 28 deletions elixir_sense/lib/elixir_sense/core/ast.ex
Original file line number Diff line number Diff line change
@@ -1,41 +1,35 @@
defmodule ElixirSense.Core.Ast do
@moduledoc """
Abstract Syntax Tree support
"""

alias ElixirSense.Core.Introspection

@empty_env_info %{requires: [], imports: [], behaviours: []}

@partials [:def, :defp, :defmodule, :@, :defmacro, :defmacrop, :defoverridable, :__ENV__, :__CALLER__, :raise, :if, :unless, :in]
@partials [:def, :defp, :defmodule, :@, :defmacro, :defmacrop, :defoverridable,
:__ENV__, :__CALLER__, :raise, :if, :unless, :in]

@max_expand_count 30000
@max_expand_count 30_000

def extract_use_info(use_ast, module) do
try do
env = Map.put(__ENV__, :module, module)
{expanded_ast, _requires} = Macro.prewalk(use_ast, {env, 1}, &do_expand/2)
{_ast, env_info} = Macro.prewalk(expanded_ast, @empty_env_info, &pre_walk_expanded/2)
env_info
rescue
_e ->
# DEBUG
# IO.puts(:stderr, "Expanding #{Macro.to_string(use_ast)} failed.")
# IO.puts(:stderr, Exception.message(e) <> "\n" <> Exception.format_stacktrace(System.stacktrace))
@empty_env_info
catch
{:expand_error, _} ->
IO.puts(:stderr, "Info: ignoring recursive macro")
@empty_env_info
end
env = Map.merge(__ENV__, %{module: module, function: nil})
{expanded_ast, _requires} = Macro.prewalk(use_ast, {env, 1}, &do_expand/2)
{_ast, env_info} = Macro.prewalk(expanded_ast, @empty_env_info, &pre_walk_expanded/2)
env_info
catch
{:expand_error, _} ->
IO.puts(:stderr, "Info: ignoring recursive macro")
@empty_env_info
end

def expand_partial(ast, env) do
try do
{expanded_ast, _} = Macro.prewalk(ast, {env, 1}, &do_expand_partial/2)
expanded_ast
rescue
_e -> ast
catch
e -> e
end
{expanded_ast, _} = Macro.prewalk(ast, {env, 1}, &do_expand_partial/2)
expanded_ast
rescue
_e -> ast
catch
e -> e
end

def expand_all(ast, env) do
Expand Down Expand Up @@ -105,8 +99,13 @@ defmodule ElixirSense.Core.Ast do
if count > @max_expand_count do
throw {:expand_error, "Cannot expand recursive macro"}
end
expanded_ast = Macro.expand(ast, env)
{expanded_ast, {env, count+1}}
try do
expanded_ast = Macro.expand(ast, env)
{expanded_ast, {env, count + 1}}
rescue
_e ->
{ast, {env, count + 1}}
end
end

defp pre_walk_expanded({:__block__, _, _} = ast, acc) do
Expand Down
Loading

0 comments on commit 79a30e9

Please sign in to comment.