-
Notifications
You must be signed in to change notification settings - Fork 196
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
WIP: Configuration file support #338
Open
axelson
wants to merge
5
commits into
elixir-lsp:master
Choose a base branch
from
axelson:config-file
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
defmodule ElixirLS.Utils.Config.SettingDef do | ||
@moduledoc """ | ||
Defines attributes for an individual setting supported by ElixirLS. | ||
""" | ||
|
||
@enforce_keys [:key, :json_key, :type, :default, :doc] | ||
defstruct [:key, :json_key, :type, :default, :doc] | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
defmodule ElixirLS.Utils.ConfigParser do | ||
@moduledoc """ | ||
Parses and loads an ElixirLS configuration file | ||
""" | ||
|
||
alias ElixirLS.Utils.Config.SettingDef | ||
|
||
@settings [ | ||
%SettingDef{ | ||
key: :dialyzer_enabled, | ||
json_key: "dialyzerEnabled", | ||
type: :boolean, | ||
default: true, | ||
doc: "Run ElixirLS's rapid Dialyzer when code is saved" | ||
}, | ||
%SettingDef{ | ||
key: :dialyzer_format, | ||
json_key: "dialyzerFormat", | ||
type: {:one_of, ["dialyzer", "dialyxir_short", "dialyxir_long"]}, | ||
default: "dialyxir_long", | ||
doc: "Formatter to use for Dialyzer warnings" | ||
}, | ||
%SettingDef{ | ||
key: :dialyzer_warn_opts, | ||
json_key: "dialyzerWarnOpts", | ||
type: | ||
{:custom, ElixirLS.Utils.NimbleListChecker, :list, | ||
[ | ||
"error_handling", | ||
"no_behaviours", | ||
"no_contracts", | ||
"no_fail_call", | ||
"no_fun_app", | ||
"no_improper_lists", | ||
"no_match", | ||
"no_missing_calls", | ||
"no_opaque", | ||
"no_return", | ||
"no_undefined_callbacks", | ||
"no_unused", | ||
"underspecs", | ||
"unknown", | ||
"unmatched_returns", | ||
"overspecs", | ||
"specdiffs" | ||
]}, | ||
default: [], | ||
doc: | ||
"Dialyzer options to enable or disable warnings. See Dialyzer's documentation for options. Note that the `race_conditions` option is unsupported" | ||
}, | ||
%SettingDef{ | ||
key: :fetch_deps, | ||
json_key: "fetchDeps", | ||
type: :boolean, | ||
default: true, | ||
doc: "Automatically fetch project dependencies when compiling" | ||
}, | ||
%SettingDef{ | ||
key: :mix_env, | ||
json_key: "mixEnv", | ||
type: :string, | ||
default: "test", | ||
doc: "Mix environment to use for compilation" | ||
}, | ||
%SettingDef{ | ||
key: :mix_target, | ||
json_key: "mixTarget", | ||
type: :string, | ||
default: "host", | ||
doc: "Mix target (`MIX_TARGET`) to use for compilation (requires Elixir >= 1.8)" | ||
}, | ||
%SettingDef{ | ||
key: :project_dir, | ||
json_key: "projectDir", | ||
type: :string, | ||
default: "", | ||
doc: | ||
"Subdirectory containing Mix project if not in the project root. " <> | ||
"If value is \"\" then defaults to the workspace rootUri." | ||
}, | ||
%SettingDef{ | ||
key: :suggest_specs, | ||
json_key: "suggestSpecs", | ||
type: :boolean, | ||
default: true, | ||
doc: | ||
"Suggest @spec annotations inline using Dialyzer's inferred success typings " <> | ||
"(Requires Dialyzer)" | ||
}, | ||
%SettingDef{ | ||
key: :trace, | ||
json_key: "trace", | ||
type: :map, | ||
default: %{}, | ||
doc: "Ignored" | ||
} | ||
] | ||
|
||
def load_config_file(path) do | ||
with {:ok, contents} <- File.read(path) do | ||
load_config(contents) | ||
end | ||
end | ||
|
||
def load_config(contents) do | ||
with {:ok, settings_map} <- json_decode(contents), | ||
{:ok, validated_options} <- parse_config(settings_map) do | ||
{:ok, Map.new(validated_options), []} | ||
end | ||
end | ||
|
||
def default_config do | ||
@settings | ||
|> Map.new(fn %SettingDef{} = setting_def -> | ||
%SettingDef{key: key, default: default} = setting_def | ||
{key, default} | ||
end) | ||
end | ||
|
||
@doc """ | ||
Parse the raw decoded JSON to the settings map (including translation from | ||
camelCase to snake_case) | ||
""" | ||
def parse_config(settings_map) do | ||
# Because we use a configuration layering approach, this configuration | ||
# parsing should be based on the settings_map and not the possible settings. | ||
# The return value should be *only* the settings that were passed in, don't | ||
# return the defaults here. | ||
values = | ||
settings_map | ||
|> Enum.map(fn {json_key, value} -> | ||
case translate_key(json_key) do | ||
{:ok, key} -> {:ok, {key, value}} | ||
{:error, "unknown key"} -> {:error, {:unrecognized_configuration_key, json_key, value}} | ||
end | ||
end) | ||
|
||
{good, errors} = Enum.split_with(values, &match?({:ok, _}, &1)) | ||
config = Map.new(good, fn {:ok, {key, val}} -> {key, val} end) | ||
|
||
{:ok, config, errors} | ||
end | ||
|
||
for %SettingDef{key: key, json_key: json_key} <- @settings do | ||
defp translate_key(unquote(json_key)) do | ||
{:ok, unquote(key)} | ||
end | ||
end | ||
|
||
defp translate_key(_), do: {:error, "unknown key"} | ||
|
||
for setting <- @settings do | ||
def valid_key?(unquote(setting.json_key)), do: true | ||
end | ||
|
||
def valid_key?(_), do: false | ||
|
||
def json_decode(contents) when is_binary(contents) do | ||
contents | ||
|> String.split(["\n", "\r", "\r\n"], trim: true) | ||
|> Enum.map(&String.trim/1) | ||
# Ignore json comments | ||
|> Enum.reject(&String.starts_with?(&1, "#")) | ||
|> Enum.join() | ||
|> JasonVendored.decode() | ||
|> case do | ||
{:ok, _} = ok -> ok | ||
{:error, %JasonVendored.DecodeError{} = err} -> {:error, {:invalid_json, err}} | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
defmodule ElixirLS.Utils.XDG do | ||
@moduledoc """ | ||
Utilities for reading files within ElixirLS's XDG configuration directory | ||
""" | ||
|
||
@default_xdg_directory "$HOME/.config" | ||
|
||
def read_elixir_ls_config_file(path) do | ||
xdg_directory() | ||
|> Path.join("elixir_ls") | ||
|> Path.join(path) | ||
|> File.read() | ||
|> case do | ||
{:ok, file_contents} -> {:ok, file_contents} | ||
err -> err | ||
end | ||
end | ||
|
||
defp xdg_directory do | ||
case System.get_env("XDG_CONFIG_HOME") do | ||
nil -> | ||
@default_xdg_directory | ||
|
||
xdg_directory -> | ||
if File.dir?(xdg_directory) do | ||
xdg_directory | ||
else | ||
raise "$XDG_CONFIG_HOME environment variable set, but directory does not exist" | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vscode jsonc (JSON with comments) supports JS style comments only (
//
and/**/
), see https://code.visualstudio.com/docs/languages/json#_json-with-commentshttps://github.com/Microsoft/node-jsonc-parser
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
haha, good catch. I've obviously haven't been doing very much js development recently. However since we're not using a full processor I don't think it's feasible to support multi line comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. A simple full line comment support is good enough.