Packages
spark
0.1.26
2.7.2
2.7.1
2.7.0
2.6.1
2.6.0
2.5.0
2.4.1
2.4.0
2.3.14
2.3.13
2.3.12
2.3.11
2.3.10
2.3.9
2.3.8
2.3.7
2.3.6
2.3.5
2.3.4
2.3.3
2.3.2
2.3.1
2.3.0
2.2.69
2.2.68
2.2.67
2.2.66
2.2.65
2.2.64
2.2.63
2.2.62
2.2.61
2.2.60
2.2.59
2.2.58
2.2.57
2.2.56
2.2.55
2.2.54
2.2.53
2.2.52
2.2.51
2.2.50
2.2.49
2.2.48
2.2.47
2.2.46
2.2.45
2.2.44
2.2.43
2.2.42
2.2.41
2.2.40
2.2.39
2.2.38
2.2.37
2.2.36
2.2.35
2.2.34
2.2.33
2.2.32
2.2.31
2.2.30
2.2.29
2.2.28
2.2.27
2.2.26
2.2.25
2.2.24
2.2.23
2.2.22
2.2.21
2.2.20
2.2.19
2.2.18
2.2.17
2.2.16
2.2.15
2.2.14
2.2.13
2.2.12
2.2.11
2.2.10
2.2.9
2.2.8
2.2.7
2.2.6
2.2.5
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.1.24
2.1.23
2.1.22
2.1.21
2.1.20
2.1.19
2.1.18
2.1.17
2.1.16
2.1.15
2.1.14
2.1.13
2.1.12
2.1.11
2.1.10
2.1.9
2.1.8
2.1.7
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.0.1
2.0.0
1.1.55
1.1.54
1.1.53
1.1.52
1.1.51
1.1.50
1.1.49
1.1.48
1.1.47
1.1.46
1.1.45
1.1.44
1.1.43
1.1.42
1.1.41
1.1.40
1.1.39
1.1.38
1.1.37
1.1.36
1.1.35
1.1.34
1.1.32
1.1.31
1.1.30
1.1.29
1.1.28
1.1.27
1.1.26
1.1.25
1.1.24
1.1.22
1.1.21
1.1.20
1.1.19
1.1.18
1.1.17
1.1.16
1.1.15
1.1.14
retired
1.1.13
1.1.12
1.1.11
1.1.10
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.18
0.2.17
0.2.16
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.29
0.1.28
0.1.27
0.1.26
0.1.25
0.1.24
0.1.23
0.1.22
0.1.21
0.1.20
0.1.19
0.1.18
0.1.17
0.1.16
0.1.15
0.1.14
0.1.13
0.1.12
0.1.11
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
Generic tooling for building DSLs
Current section
Files
Jump to
Current section
Files
lib/spark/elixir_sense/plugin.ex
if Code.ensure_loaded?(ElixirSense.Plugin) do
defmodule Spark.ElixirSense.Plugin do
@moduledoc false
@behaviour ElixirSense.Plugin
use ElixirSense.Providers.Suggestion.GenericReducer
alias ElixirSense.Providers.Suggestion.Matcher
alias Spark.ElixirSense.Entity
alias Spark.ElixirSense.Types
def suggestions(hint, {_, function_call, arg_index, info}, _chain, opts) do
opts = add_module_store(opts)
option = info.option || get_option(opts.cursor_context.text_before)
if option do
get_suggestions(hint, opts, [function_call], {:value, option})
else
get_suggestions(hint, opts, [function_call], {:arg, arg_index})
end
end
def suggestions(hint, opts) do
opts = add_module_store(opts)
option = get_section_option(opts.cursor_context.text_before)
if option do
get_suggestions(hint, opts, [], {:value, option})
else
get_suggestions(hint, opts)
end
end
# For some reason, the module store does not change when modules are defined
# so we are building our own fresh copy here. This is definitely a performance
# hit
defp add_module_store(opts) do
Map.put(opts, :module_store, ElixirSense.Core.ModuleStore.build(all_loaded()))
end
defp all_loaded do
:code.all_loaded()
|> Enum.filter(fn
{mod, _} when is_atom(mod) -> true
_ -> false
end)
|> Enum.map(&elem(&1, 0))
end
def get_suggestions(hint, opts, opt_path \\ [], type \\ nil) do
with true <- Enum.any?(opts.env.attributes, &(&1.name == :spark_is)),
dsl_mod when not is_nil(dsl_mod) <-
Enum.find(opts.env.requires, &spark_extension?/1) do
extension_kinds =
List.flatten(dsl_mod.module_info[:attributes][:spark_extension_kinds] || [])
extensions = default_extensions(dsl_mod) ++ parse_extensions(opts, extension_kinds)
scopes_to_lines =
Enum.reduce(opts.buffer_metadata.lines_to_env, %{}, fn {line, env}, acc ->
line = line - 1
Map.update(acc, env.scope_id, line, fn existing ->
if existing < line do
existing
else
line
end
end)
end)
scope_path = get_scope_path(opts, scopes_to_lines, nil, opt_path)
case get_constructors(extensions, scope_path, hint, type) do
[] ->
:ignore
constructors ->
suggestions =
case find_option(constructors, type) do
{key, config} ->
option_values(key, config, hint, opts)
_ ->
# Check for an edge case where we are editing the first argument of a constructor
with {:value, option} <- type,
entity when not is_nil(entity) <-
find_building_entity(constructors, option),
[arg | _] <- entity.args,
config when not is_nil(config) <- entity.schema[arg] do
option_values(arg, config, hint, opts)
else
_ ->
hint =
case type do
{:value, val} when not is_nil(val) ->
to_string(val)
_ ->
nil
end
Enum.map(constructors, fn
{key, config} ->
option_suggestions(key, config, type)
%{__struct__: Spark.Dsl.Entity} = entity ->
entity_suggestions(entity)
%{__struct__: Spark.Dsl.Section} = section ->
section_suggestions(section)
end)
|> filter_matches(hint)
end
end
{:override, List.flatten(suggestions)}
end
else
_ ->
:ignore
end
end
defp filter_matches(hints, match) do
if match do
Enum.filter(hints, fn %{label: label} ->
Matcher.match?(label, match)
end)
else
hints
end
end
defp find_building_entity(constructors, option) do
Enum.find(constructors, fn
%{__struct__: Spark.Dsl.Entity, name: ^option} ->
true
_ ->
false
end)
end
defp find_option(constructors, {:value, option}) do
Enum.find_value(constructors, fn
{^option, _} = opt ->
opt
%{__struct__: Spark.Dsl.Entity, name: ^option, args: [arg | _], schema: schema} ->
{arg, schema[arg]}
_ ->
false
end)
end
defp find_option(constructors, {:arg, arg_index}) do
Enum.find(constructors, fn
{_option, config} ->
config[:arg_index] == arg_index
_ ->
false
end)
end
defp find_option(_, _), do: nil
defp section_suggestions(section) do
snippet = Map.get(section, :snippet)
snippet =
if snippet && snippet != "" do
snippet
else
"$0"
end
%{
type: :generic,
kind: :function,
label: to_string(section.name),
snippet: "#{section.name} do\n #{snippet}\nend",
detail: "Dsl Section",
documentation: Map.get(section, :docs) || ""
}
end
defp entity_suggestions(entity) do
snippet = Map.get(entity, :snippet)
snippet =
if snippet && snippet != "" do
snippet
else
"$0"
end
%{
type: :generic,
kind: :function,
label: to_string(entity.name),
snippet: "#{entity.name} #{args(entity)}do\n #{snippet}\nend",
detail: "Dsl Entity",
documentation: Map.get(entity, :docs) || ""
}
end
defp option_suggestions(key, config, type) do
snippet =
if config[:snippet] && config[:snippet] != "" do
config[:snippet]
else
default_snippet(config)
end
snippet =
case type do
:option ->
"#{key}: #{snippet}"
{:arg, _} ->
"#{key}: #{snippet}"
_ ->
"#{key} #{snippet}"
end
%{
type: :generic,
kind: :function,
label: to_string(key),
snippet: snippet,
detail: "Option",
documentation: config[:doc]
}
end
defp get_option(text) when is_binary(text) do
case Regex.named_captures(~r/\s(?<option>[^\s]*):[[:blank:]]*$/, text) do
%{"option" => option} when option != "" ->
try do
String.to_existing_atom(option)
rescue
_ ->
nil
end
_ ->
nil
end
end
defp get_option(_), do: nil
defp get_section_option(text) when is_binary(text) do
case Regex.named_captures(~r/\n[[:blank:]]+(?<option>[^\s]*)[[:blank:]]*$/, text) do
%{"option" => option} when option != "" ->
try do
String.to_existing_atom(option)
rescue
_ ->
nil
end
_ ->
nil
end
end
defp get_section_option(_), do: nil
defp option_values(key, config, hint, opts) do
case config[:type] do
:boolean ->
enum_suggestion([true, false], hint, "boolean")
{:one_of, values} ->
enum_suggestion(values, hint, to_string(key))
{:in, values} ->
enum_suggestion(values, hint, to_string(key))
{:spark_type, module, func} ->
Types.find_builtin_types(module, func, hint, opts.cursor_context) ++
Types.find_custom_types(module, func, hint, opts.module_store)
{:spark_type, module, func, templates} ->
Types.find_builtin_types(module, func, hint, opts.cursor_context, templates) ++
Types.find_custom_types(module, func, hint, opts.module_store)
{:spark, type} ->
Entity.find_entities(type, hint)
{:spark_behaviour, behaviour, builtins} ->
Entity.find_spark_behaviour_impls(behaviour, builtins, hint, opts.module_store)
{:behaviour, behaviour} ->
Entity.find_spark_behaviour_impls(behaviour, nil, hint, opts.module_store)
{:spark_behaviour, behaviour} ->
Entity.find_spark_behaviour_impls(behaviour, nil, hint, opts.module_store)
_ ->
[]
end
end
defp enum_suggestion(values, hint, label) do
Enum.flat_map(values, fn v ->
inspected = inspect(v)
if Matcher.match?(inspected, hint) do
[
%{
type: :generic,
kind: :type_parameter,
label: label,
insert_text: inspect(v),
snippet: inspect(v),
documentation: inspect(v),
priority: 0
}
]
else
[]
end
end)
end
defp default_snippet(config) do
cond do
config[:type] == :boolean && config[:default] in [true, false] ->
"#{to_string(!config[:default])}"
config[:type] == :string ->
"\"$0\""
match?({:list, {:in, _list}}, config[:type]) ->
if config[:default] do
inspect(config[:default])
else
"$0"
end
match?({:list, _}, config[:type]) ->
"[$0]"
match?({:in, _}, config[:type]) ->
if config[:default] do
inspect(config[:default])
else
{:in, list} = config[:type]
inspect(Enum.at(list, 0))
end
match?({:one_of, _}, config[:type]) ->
if config[:default] do
inspect(config[:default])
else
{:one_of, list} = config[:type]
inspect(Enum.at(list, 0))
end
config[:type] == :atom ->
":$0"
config[:type] == :mfa ->
"{${1:module}, :${2:function}, [${3:args}]}"
config[:type] == :keyword_list ->
"[$0]"
true ->
"$0"
end
end
defp args(entity) do
case entity.args || [] do
[] ->
""
args ->
args
|> Enum.with_index()
|> Enum.map(fn {arg, index} ->
"${#{index + 1}:#{arg}}"
end)
|> Enum.intersperse(", ")
|> Enum.join()
|> Kernel.<>(" ")
end
end
defp get_constructors(extensions, [], hint, _type) do
Enum.flat_map(
extensions,
fn extension ->
try do
Enum.filter(extension.sections(), fn section ->
Matcher.match?(to_string(section.name), hint)
end)
rescue
_ ->
[]
end
end
)
end
defp get_constructors(extensions, [first | rest], hint, type) do
Enum.flat_map(
extensions,
fn extension ->
try do
Enum.flat_map(extension.sections(), fn section ->
if section.name == first do
do_find_constructors(section, rest, hint, type)
else
[]
end
end)
rescue
_ ->
[]
end
end
)
end
defp do_find_constructors(entity_or_section, path, hint, type, recursives \\ [])
defp do_find_constructors(
%{__struct__: Spark.Dsl.Entity} = entity,
[],
hint,
type,
recursives
) do
case type do
{:value, _value} ->
entity.schema ++
Enum.flat_map(entity.entities || [], &elem(&1, 1)) ++
Enum.uniq(recursives ++ List.wrap(recursive_for(entity)))
{:arg, arg_index} ->
if arg_index >= Enum.count(entity.args || []) do
find_opt_hints(entity, hint) ++
find_entity_hints(entity, hint, recursives)
else
Enum.map(entity.schema, fn {key, value} ->
arg_index = Enum.find_index(entity.args || [], &(&1 == key))
if arg_index do
{key, Keyword.put(value, :arg_index, arg_index)}
else
{key, value}
end
end) ++
Enum.uniq(
recursives ++
List.wrap(recursive_for(entity))
)
end
_ ->
find_opt_hints(entity, hint) ++
find_entity_hints(entity, hint, recursives)
end
end
defp do_find_constructors(section, [], hint, _type, recursives) do
find_opt_hints(section, hint) ++
find_entity_hints(section, hint, []) ++
Enum.filter(section.sections, fn section ->
Matcher.match?(to_string(section.name), hint)
end) ++ recursives
end
defp do_find_constructors(
%{__struct__: Spark.Dsl.Entity} = entity,
[next | rest],
hint,
type,
recursives
) do
entity.entities
|> Kernel.||([])
|> Enum.flat_map(&elem(&1, 1))
|> Enum.concat(recursives)
|> Enum.concat(List.wrap(recursive_for(entity)))
|> Enum.filter(&(&1.name == next))
|> Enum.flat_map(
&do_find_constructors(
&1,
rest,
hint,
type,
Enum.uniq(recursives ++ List.wrap(recursive_for(entity)))
)
)
|> Enum.uniq()
end
defp do_find_constructors(section, [first | rest], hint, type, recursives) do
Enum.flat_map(section.entities, fn entity ->
if entity.name == first do
do_find_constructors(entity, rest, hint, type)
else
[]
end
end) ++
Enum.flat_map(section.sections, fn section ->
if section.name == first do
do_find_constructors(section, rest, hint, type)
else
[]
end
end) ++ recursives
end
defp recursive_for(entity) do
if entity.recursive_as do
entity
end
end
defp find_opt_hints(%{__struct__: Spark.Dsl.Entity} = entity, hint) do
Enum.flat_map(entity.schema, fn {key, value} ->
if Matcher.match?(to_string(key), hint) do
arg_index = Enum.find_index(entity.args || [], &(&1 == key))
if arg_index do
[{key, Keyword.put(value, :arg_index, arg_index)}]
else
[{key, value}]
end
else
[]
end
end)
end
defp find_opt_hints(section, hint) do
Enum.filter(section.schema, fn {key, _value} ->
Matcher.match?(to_string(key), hint)
end)
end
defp find_entity_hints(%{__struct__: Spark.Dsl.Entity} = entity, hint, recursives) do
entity.entities
|> Keyword.values()
|> Enum.concat(recursives)
|> Enum.concat(List.wrap(recursive_for(entity)))
|> List.flatten()
|> Enum.filter(fn entity ->
Matcher.match?(to_string(entity.name), hint)
end)
end
defp find_entity_hints(section, hint, _recursives) do
Enum.filter(section.entities, fn entity ->
Matcher.match?(to_string(entity.name), hint)
end)
end
defp get_scope_path(opts, scopes_to_lines, env, path) do
env = env || opts.env
with earliest_line when not is_nil(earliest_line) <-
scopes_to_lines[env.scope_id],
[%{func: func}] when func != :defmodule <-
opts.buffer_metadata.calls[earliest_line],
next_env when not is_nil(next_env) <-
opts.buffer_metadata.lines_to_env[earliest_line] do
get_scope_path(opts, scopes_to_lines, next_env, [func | path])
else
_ ->
path
end
end
defp spark_extension?(module) do
true in List.wrap(module.module_info()[:attributes][:spark_dsl])
rescue
_ -> false
end
defp default_extensions(dsl_mod) do
dsl_mod.module_info[:attributes][:spark_default_extensions]
|> List.wrap()
|> List.flatten()
end
defp parse_extensions(opts, extension_kinds) do
Enum.flat_map([:extensions | extension_kinds], fn extension_kind ->
case Regex.named_captures(
~r/#{extension_kind}:\s+?(?<extensions>(\[[^\]]*\])|([^\s]*))?[\,\s$]/,
opts.cursor_context.text_before
) do
%{"extensions" => extensions} when extensions != "" ->
extensions
|> String.replace("[", "")
|> String.replace("]", "")
|> String.split(",")
|> Enum.map(&String.trim/1)
_ ->
[]
end
end)
|> Enum.uniq()
|> Enum.map(&Module.concat([&1]))
|> Enum.filter(fn module ->
try do
Code.ensure_loaded?(module)
rescue
_ ->
false
end
end)
end
end
end