Repository: sublimelsp/LSP

Issue:
Merging `AbstractPlugin` settings gives a plain dict rather than `DottedDict`
Please include in your report:

* OS and language server

macOS and a custom `AbstractPlugin`

https://github.com/REditorSupport/sublime-ide-r/blob/master/ride/lsp.py#L23

* How you installed LSP (Package Control or from git?)

The branch st-4000-exploration

* Minimal reproduction steps

In `LSP.sublime-settings`, I put something like

```
		"rlang":
		{
			"enabled": true,
			"settings": {
				"debug": true
			}
		}
```


* Log

```
LSP: applying .sublime-project override for rlang
Traceback (most recent call last):
  File "/Applications/Sublime Text.app/Contents/MacOS/Lib/python33/sublime_plugin.py", line 220, in unload_module
    module.plugin_unloaded()
  File "/Users/Randy/Dropbox/Sublime Text/Packages/LSP/plugin/core/main.py", line 131, in plugin_unloaded
    unload_sessions(window)  # unloads view state from document sync and diagnostics
  File "/Users/Randy/Dropbox/Sublime Text/Packages/LSP/plugin/core/registry.py", line 68, in unload_sessions
    wm = windows.lookup(window)
  File "/Users/Randy/Dropbox/Sublime Text/Packages/LSP/plugin/core/windows.py", line 645, in lookup
    window_configs = self._configs.for_window(window)
  File "/Users/Randy/Dropbox/Sublime Text/Packages/LSP/plugin/core/configurations.py", line 18, in for_window
    window_configs = WindowConfigManager(window, self._configs)
  File "/Users/Randy/Dropbox/Sublime Text/Packages/LSP/plugin/core/configurations.py", line 33, in __init__
    self.all = self._create_window_configs()
  File "/Users/Randy/Dropbox/Sublime Text/Packages/LSP/plugin/core/configurations.py", line 78, in _create_window_configs
    return [self._apply_project_overrides(c, project_clients_dict) for c in self._global_configs]
  File "/Users/Randy/Dropbox/Sublime Text/Packages/LSP/plugin/core/configurations.py", line 78, in <listcomp>
    return [self._apply_project_overrides(c, project_clients_dict) for c in self._global_configs]
  File "/Users/Randy/Dropbox/Sublime Text/Packages/LSP/plugin/core/configurations.py", line 84, in _apply_project_overrides
    settings = DottedDict(deepcopy(client_config.settings.get()))
```


I did some quick testing to reveal that `client_config.settings` is simple a dict `{"debug": true}` rather than a `DottedDict`. Perhaps when merging the settings from `LSP.sublime-settings` and `AbstractPlugin`, `settings` was not converted to `DottedDict`?

ref: https://github.com/sublimelsp/LSP/blob/f5e38feed10844db51adb7039caf74e9dc3f39b1/plugin/core/settings.py#L285-L285


Discussion:
By the way,

~~Which of the `LSP.sublime-settings` and `AbstractPlugin ` has higher precedence?~~

I figured it out. `LSP.sublime-settings` has a higher precedence.

Resolving patch:
diff --git a/plugin/core/settings.py b/plugin/core/settings.py
--- a/plugin/core/settings.py
+++ b/plugin/core/settings.py
@@ -2,6 +2,7 @@
 from .logging import debug
 from .types import Settings, ClientConfig, LanguageConfig, syntax2scope
 from .typing import Any, List, Optional, Dict, Callable, Union
+from copy import deepcopy
 import sublime
 
 
@@ -276,6 +277,12 @@ def update_client_config(external_config: ClientConfig, user_override_config: Di
     user_override_languages = read_language_configs(user_override_config)
     if not user_override_languages:
         user_override_languages = external_config.languages
+    user_override_settings = user_override_config.get("settings")
+    if isinstance(user_override_settings, dict):
+        settings = DottedDict(deepcopy(external_config.settings.get()))
+        settings.update(user_override_settings)
+    else:
+        settings = external_config.settings
     return ClientConfig(
         name=external_config.name,
         binary_args=user_override_config.get("command", external_config.binary_args),
@@ -283,7 +290,7 @@ def update_client_config(external_config: ClientConfig, user_override_config: Di
         tcp_port=user_override_config.get("tcp_port", external_config.tcp_port),
         enabled=user_override_config.get("enabled", external_config.enabled),
         init_options=user_override_config.get("init_options", external_config.init_options),
-        settings=user_override_config.get("settings", external_config.settings),
+        settings=settings,
         env=user_override_config.get("env", external_config.env),
         tcp_host=user_override_config.get("tcp_host", external_config.tcp_host),
         tcp_mode=user_override_config.get("tcp_mode", external_config.tcp_mode),


Test patch: