Repository: sublimelsp/LSP

Issue:
Unwanted splitting on dot of the setting keys
**Describe the bug**

In LSP-yaml, the user can specify schema mapping like so:

```
yaml.schemas: {
    "https://json.schemastore.org/composer": "/*"
}
```

This doesn't work in ST because LSP splits the URL key on every dot, creating an object that the server doesn't understand.

**Expected behavior**

Should send the value of the `yaml.schemas` object without altering it

**Additional context**

I'm not sure how VSCode manages to handle it but I would guess it is somewhat taking into consideration the schema itself. We could try to check that first.


Discussion:
Tried looking for the part where they split the settings. Maybe this will help. 

https://github.com/microsoft/vscode-languageserver-node/blob/f5b306a318246ae71a8275406e0c5211c5c67de9/client/src/common/client.ts#L2541-L2573
That code is used by language servers and works the same regardless of the editor used. The relevant code would be somewhere in the vscode repo, I would think.

Basically, the issue already happens at the point of parsing the settings and storing them in the model. We split every key on a dot and that is not reversible later. So we would need to avoid that at the time of parsing.
I think we can just check for the presence of a `:` or `/` character. Nobody uses those characters for their key (part), no?
So I was trying to figure out how they parse the settings and I get stuck around this. They don't seem to do anything special with the settings. I guess we need someone who can read typescript better 😅

https://github.com/microsoft/vscode/blob/main/src/vs/platform/configuration/common/configuration.ts#L303-L322


> I think we can just check for the presence of a `:` or `/` character. Nobody uses those characters for their key (part), no?

As I previously mentioned in discord, I have not seen settings that would use `:` or `/` or any other character that would serve as a separator aside from `.` nor a character for a "settings key" (e.g. `sett!ng.n@me`). Normally, it would be alphanumeric characters separated by a dot.

In a context that the value being checked is a "settings key", we could assume that when the value matches a URL-like pattern it would already indicate that we don't need to "split" it.


Resolving patch:
diff --git a/plugin/core/collections.py b/plugin/core/collections.py
--- a/plugin/core/collections.py
+++ b/plugin/core/collections.py
@@ -135,13 +135,10 @@ def update(self, d: Dict[str, Any]) -> None:
         """
         Overwrite and/or add new key-value pairs to the collection.
 
-        :param      d:    The overriding dictionary. Can contain nested dictionaries.
+        :param      d:    The overriding dictionary. Keys must be in the new-style dotted format.
         """
         for key, value in d.items():
-            if isinstance(value, dict):
-                self._update_recursive(value, key)
-            else:
-                self.set(key, value)
+            self.set(key, value)
 
     def get_resolved(self, variables: Dict[str, str]) -> Dict[str, Any]:
         """
@@ -153,16 +150,6 @@ def get_resolved(self, variables: Dict[str, str]) -> Dict[str, Any]:
         """
         return sublime.expand_variables(self._d, variables)
 
-    def _update_recursive(self, current: Dict[str, Any], prefix: str) -> None:
-        if not current:
-            return self.set(prefix, current)
-        for key, value in current.items():
-            path = "{}.{}".format(prefix, key)
-            if isinstance(value, dict):
-                self._update_recursive(value, path)
-            else:
-                self.set(path, value)
-
     def __repr__(self) -> str:
         return "{}({})".format(self.__class__.__name__, repr(self._d))
 


Test patch:
diff --git a/tests/test_collections.py b/tests/test_collections.py
--- a/tests/test_collections.py
+++ b/tests/test_collections.py
@@ -74,12 +74,8 @@ def test_update(self) -> None:
         d.set("foo.bar.c", "c")
         self.verify(d, "foo.bar", {"a": "a", "b": "b", "c": "c"})
         d.update({
-            "foo": {
-                "bar": {
-                    "a": "x",
-                    "b": "y"
-                }
-            }
+            "foo.bar.a": "x",
+            "foo.bar.b": "y"
         })
         self.verify(d, "foo.bar", {"a": "x", "b": "y", "c": "c"})
 
@@ -137,3 +133,45 @@ def test_update_empty_dict(self) -> None:
         self.assertEqual(d.get(), {"a": {}})
         d.update({"a": {"b": {}}})
         self.assertEqual(d.get(), {"a": {"b": {}}})
+
+    def test_from_base_and_override(self) -> None:
+        base = DottedDict({
+            "yaml.schemas": {}
+        })
+        override = {
+            "yaml.schemas": {
+                "http://foo.com/bar.json": "**/*.json"
+            }
+        }
+        result = DottedDict.from_base_and_override(base, override)
+        self.assertEqual(
+            result.get(None),
+            {
+                "yaml": {
+                    "schemas": {
+                        "http://foo.com/bar.json": "**/*.json"
+                    }
+                }
+            }
+        )
+
+    def test_update_with_dicts(self) -> None:
+        base = {
+            "settings": {
+                "yaml.schemas": {}
+            }
+        }
+        overrides = {
+            "yaml.schemas": {
+                "http://foo.com/bar.json": "**/*.json"
+            }
+        }
+        settings = DottedDict(base.get("settings", {}))
+        settings.update(overrides)
+        self.assertEqual(settings.get(), {
+            "yaml": {
+                "schemas": {
+                    "http://foo.com/bar.json": "**/*.json"
+                }
+            }
+        })