Packages
spark
1.1.43
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
defmodule Spark.ElixirSense.Plugin do
@moduledoc false
Module.register_attribute(__MODULE__, :is_elixir_sense_plugin, persist: true)
@is_elixir_sense_plugin true
case Code.ensure_compiled(ElixirSense.Plugin) do
{:module, _} ->
@behaviour ElixirSense.Plugin
@behaviour ElixirSense.Providers.Suggestion.GenericReducer
_ ->
:ok
end
def reduce(hint, env, buffer_metadata, cursor_context, acc) do
apply(ElixirSense.Providers.Suggestion.GenericReducer, :reduce, [
__MODULE__,
hint,
env,
buffer_metadata,
cursor_context,
acc
])
end
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)
{path, type} =
if function_call == :use || option == :do || !info.cursor_at_option do
{[], nil}
else
type =
case option do
nil ->
{:arg, arg_index}
option ->
{:value, option}
end
{[function_call], type}
end
get_suggestions(hint, opts, path, type)
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, apply(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
is_spark_entity? = is_dsl?(opts.env) || is_fragment?(opts.env)
with true <- is_spark_entity?,
dsl_mod when not is_nil(dsl_mod) <- get_dsl_mod(opts) 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 | _] <- Spark.Dsl.Entity.arg_names(entity),
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} ->
apply(Matcher, :match?, [label, match])
end)
else
hints
end
end
defp is_dsl?(env) do
Enum.any?(env.attributes, &(&1.name == :spark_is)) ||
Enum.any?(env.requires, fn module ->
try do
module.module_info(:attributes)
|> Enum.any?(fn
{:spark_dsl, [true]} ->
true
_ ->
false
end)
rescue
_ -> false
end
end)
end
defp is_fragment?(env) do
Enum.any?(env.attributes, &(&1.name == :spark_fragment_of)) ||
Spark.Dsl.Fragment in env.requires
end
defp get_dsl_mod(opts) do
if Spark.Dsl.Fragment in opts.env.requires do
parse_fragment(opts)
else
Enum.find(opts.env.requires, &spark_extension?/1)
end
end
defp parse_fragment(opts) do
case Regex.named_captures(
~r/of:\s+?(?<of>|([^\s]*))?[\,\s$]/,
opts.cursor_context.text_before
) do
%{"of" => dsl} when dsl != "" ->
module = Module.concat([dsl])
if Code.ensure_loaded?(module) do
module
end
_ ->
nil
end
rescue
_ -> false
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
%{
type: :generic,
kind: :function,
label: to_string(entity.name),
snippet: "#{entity.name} #{args(entity)}",
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
config = Spark.OptionsHelpers.update_key_docs(config)
%{
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)
{:spark_function_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)
{:spark_function_behaviour, behaviour, _} ->
Entity.find_spark_behaviour_impls(behaviour, nil, hint, opts.module_store)
{:or, subtypes} ->
Enum.flat_map(subtypes, fn subtype ->
option_values(key, Keyword.put(config, :type, subtype), hint, opts)
end)
_ ->
[]
end
end
defp enum_suggestion(values, hint, label) do
Enum.flat_map(values, fn v ->
inspected = inspect(v)
if apply(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, _}, config[:type]) ->
{_, inner_type} = config[:type]
"[#{default_snippet(type: inner_type)}]"
match?({:wrap_list, _}, config[:type]) ->
{_, inner_type} = config[:type]
default_snippet(type: inner_type)
match?({in_type, _} when in_type in [:in, :one_of], config[:type]) ->
{_, list} = config[:type]
if is_nil(config[:default]) do
{_, list} = config[:type]
inspect(Enum.at(list, 0))
else
list |> Enum.reject(&(&1 == config[:default])) |> Enum.at(0) |> inspect()
end
match?({:tagged_tuple, _, _}, config[:type]) ->
{:tagged_tuple, tag, inner_type} = config[:type]
"{:#{tag}, #{default_snippet(inner_type)}}"
config[:type] == :atom ->
":$0"
match?({:mfa_or_fun, _}, config[:type]) ->
{:mfa_or_fun, arity} = config[:type]
default_snippet(type: {:or, [{:fun, arity}, :mfa]})
match?({:literal, _}, config[:type]) ->
{:literal, value} = config[:type]
inspect(value)
match?({:fun, 0}, config[:type]) ->
"""
fn ->
${0:body}
end
"""
match?({:fun, _}, config[:type]) ->
{:fun, arity} = config[:type]
args = Enum.map_join(0..(arity - 1), ", ", &"arg#{&1 + 1}")
"""
fn #{args} ->
${#{arity}:body}
end
"""
match?(:map, config[:type]) || match?({:map, _, _}, config[:type]) ->
"%{${1:key} => ${2:value}}"
match?(:keyword_list, config[:type]) ->
"[${1:key} => ${2:value}]"
config[:type] == :mfa ->
"{${1:module}, :${2:function}, [${3:args}]}"
config[:type] == :keyword_list ->
"[$0]"
true ->
"$0"
end
end
defp args(entity) do
case Spark.Dsl.Entity.arg_names(entity) do
[] ->
""
args ->
args
|> Enum.with_index()
|> Enum.map(fn {arg, index} ->
"${#{index + 1}:#{arg}}"
end)
|> Enum.intersperse(", ")
|> Enum.join()
end
end
defp get_constructors(extensions, [], hint, type) do
Enum.flat_map(
extensions,
fn extension ->
try do
top_level_constructors =
extension
|> sections()
|> Enum.filter(& &1.top_level?)
|> Enum.flat_map(fn section ->
do_find_constructors(section, [], hint, type)
end)
extension.sections()
|> apply_dsl_patches(extensions)
|> Enum.filter(fn section ->
!section.top_level? &&
apply(Matcher, :match?, [to_string(section.name), hint])
end)
|> Enum.concat(top_level_constructors)
rescue
_ ->
[]
end
end
)
end
defp get_constructors(extensions, [first | rest], hint, type) do
extensions
|> Enum.flat_map(§ions/1)
|> Enum.filter(& &1.top_level?)
|> Enum.find(fn section ->
Enum.any?(section.sections, &(&1.name == first)) ||
Enum.any?(section.entities, &(&1.name == first))
end)
|> case do
nil ->
Enum.flat_map(
extensions,
fn extension ->
try do
Enum.flat_map(apply_dsl_patches(sections(extension), extensions), fn section ->
if section.name == first do
do_find_constructors(section, rest, hint, type)
else
[]
end
end)
rescue
_ ->
[]
end
end
)
top_level_section ->
do_find_constructors(top_level_section, [first | rest], hint, type)
end
end
defp apply_dsl_patches(sections_or_entities, extensions, path \\ [])
defp apply_dsl_patches(sections_or_entities, extensions, path)
when is_list(sections_or_entities) do
Enum.map(sections_or_entities, &apply_dsl_patches(&1, extensions, path))
end
defp apply_dsl_patches(%Spark.Dsl.Entity{} = entity, _, _), do: entity
defp apply_dsl_patches(%Spark.Dsl.Section{} = section, extensions, path) do
section_path_matcher = path ++ [section.name]
new_entities =
Enum.flat_map(extensions, fn extension ->
extension
|> dsl_patches()
|> Enum.flat_map(fn
%Spark.Dsl.Patch.AddEntity{section_path: ^section_path_matcher, entity: entity} ->
[entity]
_ ->
[]
end)
end)
%{
section
| sections:
Enum.map(section.sections, &apply_dsl_patches(&1, extensions, path ++ [section.name])),
entities: section.entities ++ new_entities
}
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(Spark.Dsl.Entity.arg_names(entity), &(&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 ->
apply(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 apply(Matcher, :match?, [to_string(key), hint]) do
arg_index = Enum.find_index(Spark.Dsl.Entity.arg_names(entity), &(&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} ->
apply(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 ->
apply(Matcher, :match?, [to_string(entity.name), hint])
end)
end
defp find_entity_hints(section, hint, _recursives) do
Enum.filter(section.entities, fn entity ->
apply(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
_other ->
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
defp sections(extension) do
extension.sections()
rescue
_ ->
[]
end
defp dsl_patches(extension) do
extension.dsl_patches()
rescue
_ ->
[]
end
end