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
@@ -113,7 +113,9 @@ def __bool__(self) -> bool:
         """
         return bool(self._d)
 
-    def __contains__(self, path: str) -> bool:
+    def __contains__(self, path: object) -> bool:
+        if not isinstance(path, str):
+            return False
         value = self.get(path)
         return value is not None and value is not False
 
@@ -154,7 +156,7 @@ 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:
+        if not current or any(filter(lambda key: isinstance(key, str) and (":" in key or "/" in key), current.keys())):
             return self.set(prefix, current)
         for key, value in current.items():
             path = "{}.{}".format(prefix, key)


Test patch:
diff --git a/tests/test_collections.py b/tests/test_collections.py
--- a/tests/test_collections.py
+++ b/tests/test_collections.py
@@ -137,3 +137,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"
+                }
+            }
+        })