1- # TODO (later): move this definition to external files
2- const DEFAULT_CONFIG = ConfigDict (
3- " full_analysis" => ConfigDict (
4- " debounce" => 1.0
5- ),
6- " testrunner" => ConfigDict (
7- " executable" => @static Sys. iswindows () ? " testrunner.bat" : " testrunner"
8- ),
9- " formatter" => ConfigDict (
10- " runic" => ConfigDict (
11- " executable" => @static Sys. iswindows () ? " runic.bat" : " runic"
12- )
13- ),
14- " internal" => ConfigDict (
15- " static_setting" => 0
16- ),
17- )
18-
19- const STATIC_CONFIG = ConfigDict (
20- " full_analysis" => ConfigDict (
21- " debounce" => false
22- ),
23- " testrunner" => ConfigDict (
24- " executable" => false
25- ),
26- " formatter" => ConfigDict (
27- " runic" => ConfigDict (
28- " executable" => false
29- )
30- ),
31- " internal" => ConfigDict (
32- " static_setting" => true
33- ),
34- )
35-
36- function access_nested_dict (dict:: ConfigDict , path:: String , rest_path:: String... )
37- nextobj = @something get (dict, path, nothing ) return nothing
38- if ! (nextobj isa ConfigDict)
39- if isempty (rest_path)
40- return nextobj
41- else
42- return nothing
43- end
44- end
45- return access_nested_dict (nextobj, rest_path... )
46- end
47-
48- """
49- traverse_merge(on_leaf, base::ConfigDict, overlay::ConfigDict) -> merged::ConfigDict
50-
51- Return a new `ConfigDict` whose key value pairs are merged from `base` and `overlay`.
52-
53- If a key in `overlay` is a dictionary, it will recursively merge it into the corresponding
54- key in `base`, creating new `ConfigDict` instances along the way.
55-
56- When a value in `overlay` is not a dictionary, the `on_leaf` function is called with:
57- - `current_path`: the current path as a vector of strings
58- - `v`: the value from `overlay`
59- The `on_leaf(current_path, v) -> newv` function should return the value to be stored
60- in the result, or `nothing` to skip storing the key.
61- """
62- function traverse_merge (
63- on_leaf, base:: ConfigDict , overlay:: ConfigDict ,
64- key_path:: Vector{String} = String[]
65- )
66- result = base
67- for (k, v) in overlay
68- current_path = [key_path; k]
69- if v isa ConfigDict
70- base_v = get (base, k, nothing )
71- if base_v isa ConfigDict
72- merged_v = traverse_merge (on_leaf, base_v, v, current_path)
73- result = ConfigDict (result, k => merged_v)
74- else
75- merged_v = traverse_merge (on_leaf, ConfigDict (), v, current_path)
76- result = ConfigDict (result, k => merged_v)
77- end
78- else
79- on_leaf_result = on_leaf (current_path, v)
80- if on_leaf_result != = nothing
81- result = ConfigDict (result, k => on_leaf_result)
82- end
83- end
84- end
85- return result
86- end
87-
88- is_static_setting (key_path:: String... ) = access_nested_dict (STATIC_CONFIG, key_path... ) === true
89-
901is_config_file (filepath:: AbstractString ) = filepath == " __DEFAULT_CONFIG__" || basename (filepath) == " .JETLSConfig.toml"
912
923"""
@@ -97,7 +8,7 @@ The order is determined by the following rule:
978
9891. "__DEFAULT_CONFIG__" has lower priority than any other path.
9910
100- This rules defines a total order. (See `is_config_file`)
11+ This rule defines a total order. (See `is_config_file`)
10112"""
10213function Base. lt (:: ConfigFileOrder , path1, path2)
10314 path1 == " __DEFAULT_CONFIG__" && return false
@@ -106,49 +17,96 @@ function Base.lt(::ConfigFileOrder, path1, path2)
10617 return false # unreachable
10718end
10819
109- function cleanup_empty_dicts (dict:: ConfigDict )
110- result = dict
111- for (k, v) in dict
112- if v isa ConfigDict
113- cleaned_v = cleanup_empty_dicts (v)
114- if isempty (cleaned_v)
115- result = Base. delete (result, k)
116- elseif cleaned_v != v
117- result = ConfigDict (result, k => cleaned_v)
118- end
119- end
20+ @generated function on_difference (
21+ callback,
22+ old_config:: T ,
23+ new_config:: T ,
24+ path:: NTuple{N,Symbol} = ()
25+ ) where {T<: ConfigSection ,N}
26+ entries = (
27+ :(on_difference (
28+ callback,
29+ getfield (old_config, $ (QuoteNode (fname))),
30+ getfield (new_config, $ (QuoteNode (fname))),
31+ (path... , $ (QuoteNode (fname)))
32+ ))
33+ for fname in fieldnames (T)
34+ )
35+
36+ quote
37+ $ T ($ (entries... ))
12038 end
121- return result
12239end
12340
124- function merge_settings (base:: ConfigDict , overlay:: ConfigDict )
125- return traverse_merge (base, overlay) do _, v
126- v
127- end |> cleanup_empty_dicts
128- end
41+ @generated function on_difference (
42+ callback,
43+ old_val:: T ,
44+ new_val:: Nothing ,
45+ path:: Tuple
46+ ) where T <: ConfigSection
47+ entries = (
48+ :(on_difference (
49+ callback,
50+ getfield (old_val, $ (QuoteNode (fname))),
51+ nothing ,
52+ (path... , $ (QuoteNode (fname)))
53+ ))
54+ for fname in fieldnames (T)
55+ )
12956
130- function get_settings (data:: ConfigManagerData )
131- result = ConfigDict ()
132- for config in Iterators. reverse (values (data. watched_files))
133- result = merge_settings (result, config)
57+ quote
58+ $ T ($ (entries... ))
13459 end
135- return result
13660end
13761
138- function merge_static_settings (base:: ConfigDict , overlay:: ConfigDict )
139- return traverse_merge (base, overlay) do path, v
140- is_static_setting (path... ) ? v : nothing
141- end |> cleanup_empty_dicts
62+ @generated function on_difference (
63+ callback,
64+ old_val:: Nothing ,
65+ new_val:: T ,
66+ path:: Tuple
67+ ) where T <: ConfigSection
68+ entries = (
69+ :(on_difference (
70+ callback,
71+ nothing ,
72+ getfield (new_val, $ (QuoteNode (fname))),
73+ (path... , $ (QuoteNode (fname)))
74+ ))
75+ for fname in fieldnames (T)
76+ )
77+
78+ quote
79+ $ T ($ (entries... ))
80+ end
14281end
14382
144- function get_static_settings (data:: ConfigManagerData )
145- result = ConfigDict ()
146- for config in Iterators. reverse (values (data. watched_files))
147- result = merge_static_settings (result, config)
83+ on_difference (callback, old_val, new_val, path:: Tuple ) =
84+ old_val != = new_val ? callback (old_val, new_val, path) : old_val
85+
86+ """
87+ merge_setting(base::T, overlay::T) where {T<:ConfigSection} -> T
88+
89+ Merges two configuration objects, with `overlay` taking precedence over `base`.
90+ If a field in `overlay` is `nothing`, the corresponding field from `base` is retained.
91+ """
92+ merge_setting (base:: T , overlay:: T ) where {T<: ConfigSection } =
93+ on_difference ((base_val, overlay_val, path) -> overlay_val === nothing ? base_val : overlay_val, base, overlay)
94+
95+ function get_current_settings (watched_files:: WatchedConfigFiles )
96+ result = DEFAULT_CONFIG
97+ for config in Iterators. reverse (values (watched_files))
98+ result = merge_setting (result, config)
14899 end
149100 return result
150101end
151102
103+ # TODO : remove this.
104+ # (now this is used for `collect_unmatched_keys` only. see that's comment)
105+ const ConfigDict = Base. PersistentDict{String, Any}
106+ to_config_dict (dict:: AbstractDict ) = ConfigDict ((k => (v isa AbstractDict ? to_config_dict (v) : v) for (k, v) in dict). .. )
107+
108+ const DEFAULT_CONFIG_DICT = to_config_dict (Configurations. to_dict (DEFAULT_CONFIG))
109+
152110"""
153111 collect_unmatched_keys(this::ConfigDict, ref::ConfigDict) -> Vector{Vector{String}}
154112
@@ -178,8 +136,12 @@ julia> collect_unmatched_keys(
1781361-element Vector{Vector{String}}:
179137 ["key1"]
180138```
139+
140+
141+ TODO: remove this. This is a temporary workaround to report unknown keys in the config file
142+ until Configurations.jl supports reporting full path of unknown keys.
181143"""
182- function collect_unmatched_keys (this:: ConfigDict , ref:: ConfigDict = DEFAULT_CONFIG )
144+ function collect_unmatched_keys (this:: ConfigDict , ref:: ConfigDict = DEFAULT_CONFIG_DICT )
183145 unknown_keys = Vector{String}[]
184146 collect_unmatched_keys! (unknown_keys, this, ref, String[])
185147 return unknown_keys
@@ -211,19 +173,20 @@ Retrieves the current configuration value.
211173Among the registered configuration files, fetches the value in order of priority (see `Base.lt(::ConfigFileOrder, path1, path2)`).
212174If the key path does not exist in any of the configurations, returns `nothing`.
213175"""
214- function get_config (manager:: ConfigManager , key_path:: String... )
215- is_static_setting (key_path... ) &&
216- return access_nested_dict (load (manager). static_settings, key_path... )
217- for config in values (load (manager). watched_files)
218- return @something access_nested_dict (config, key_path... ) continue
176+ Base. @constprop :aggressive function get_config (manager:: ConfigManager , key_path:: Symbol... )
177+ try
178+ is_static_setting (key_path... ) &&
179+ return getobjpath (load (manager). static_settings, key_path... )
180+ return getobjpath (load (manager). current_settings, key_path... )
181+ catch e
182+ e isa FieldError ? nothing : rethrow ()
219183 end
220- return nothing
221184end
222185
223186function fix_static_settings! (manager:: ConfigManager )
224187 store! (manager) do old_data
225- new_static = get_static_settings (old_data)
226- new_data = ConfigManagerData (new_static, old_data. watched_files)
188+ new_static = get_current_settings (old_data. watched_files )
189+ new_data = ConfigManagerData (old_data . current_settings, new_static, old_data. watched_files)
227190 return new_data, new_static
228191 end
229192end
@@ -235,7 +198,7 @@ If the file does not exist or cannot be parsed, just return leaving the current
235198configuration unchanged. When there are unknown keys in the config file,
236199send error message while leaving current configuration unchanged.
237200"""
238- function load_config! (on_leaf , server:: Server , filepath:: AbstractString ;
201+ function load_config! (callback , server:: Server , filepath:: AbstractString ;
239202 reload:: Bool = false )
240203 store! (server. state. config_manager) do old_data
241204 if reload
@@ -247,43 +210,46 @@ function load_config!(on_leaf, server::Server, filepath::AbstractString;
247210 parsed = TOML. tryparsefile (filepath)
248211 parsed isa TOML. ParserError && return old_data, nothing
249212
250- new_config = to_config_dict (parsed)
251-
252- unknown_keys = collect_unmatched_keys (new_config)
253- if ! isempty (unknown_keys)
213+ new_config = try
214+ Configurations. from_dict (JETLSConfig, parsed)
215+ catch e
216+ # TODO : remove this when Configurations.jl support to report
217+ # full path of unknown key.
218+ if e isa Configurations. InvalidKeyError
219+ config_dict = to_config_dict (parsed)
220+ unknown_keys = collect_unmatched_keys (config_dict)
221+ if ! isempty (unknown_keys)
222+ show_error_message (server, """
223+ Configuration file at $filepath contains unknown keys:
224+ $(join (map (x -> string (' `' , join (x, " ." ), ' `' ), unknown_keys), " , " ))
225+ """ )
226+ return old_data, nothing
227+ end
228+ end
254229 show_error_message (server, """
255- Configuration file at $filepath contains unknown keys :
256- $(join ( map (x -> string ( ' ` ' , join (x, " . " ), ' ` ' ), unknown_keys), " , " ) )
230+ Failed to load configuration file at $filepath :
231+ $(e )
257232 """ )
258233 return old_data, nothing
259234 end
260235
261- current_config = get (old_data. watched_files, filepath, DEFAULT_CONFIG)
262- merged_config = traverse_merge (current_config, new_config) do filepath, v
263- on_leaf (current_config, filepath, v)
264- v
265- end
266236 new_watched_files = copy (old_data. watched_files)
267- new_watched_files[filepath] = merged_config
268- new_data = ConfigManagerData (old_data. static_settings, new_watched_files)
237+ new_watched_files[filepath] = new_config
238+ new_current_settings = get_current_settings (new_watched_files)
239+ on_difference (callback, old_data. current_settings, new_current_settings)
240+ new_data = ConfigManagerData (new_current_settings, old_data. static_settings, new_watched_files)
269241 return new_data, nothing
270242 end
271243end
272244
273- to_config_dict (dict:: Dict{String,Any} ) = ConfigDict (
274- (k => (v isa Dict{String,Any} ? to_config_dict (v) : v) for (k, v) in dict). .. )
275-
276- function delete_config! (on_leaf, manager:: ConfigManager , filepath:: AbstractString )
245+ function delete_config! (callback, manager:: ConfigManager , filepath:: AbstractString )
277246 store! (manager) do old_data
278- old_settings = get_settings (old_data)
247+ haskey (old_data. watched_files, filepath) || return old_data, nothing
279248 new_watched_files = copy (old_data. watched_files)
280249 delete! (new_watched_files, filepath)
281- new_data = ConfigManagerData (old_data. static_settings, new_watched_files)
282- new_settings = get_settings (new_data)
283- traverse_merge (old_settings, new_settings) do path, v
284- on_leaf (old_settings, path, v)
285- nothing
286- end
250+ new_current_settings = get_current_settings (new_watched_files)
251+ new_data = ConfigManagerData (new_current_settings, old_data. static_settings, new_watched_files)
252+ on_difference (callback, old_data. current_settings, new_current_settings)
287253 return new_data, nothing
288254 end
289255end
0 commit comments