Repository: python-lsp/python-lsp-server

Issue:
Maybe use initializationOptions as additional source of settings
This came up in [this issue](https://github.com/kak-lsp/kak-lsp/issues/611) with the kak-lsp client.
Before the workaround for that issue, kak-lsp sent server configuration via the `initialize` request (in `initializationOptions`).
It only sent `workspace/didChangeConfiguration` when the configuration actually changed.
This caused problems because pylsp ignores `initializationOptions`. The workaround is to re-send options with `workspace/didChangeConfiguration`.

I wonder if it's a good idea to use `initializationOptions` as additional settings source.
I see two possible variants:
1. Always include `initializationOptions` but let `workspace/didChangeConfiguration` override its settings
2. As soon as `workspace/didChangeConfiguration` arrives, forget the `initializationOptions`

Annoyingly, option 2 would not have fixed the kak-lsp issue, because kak-lsp used to an empty 
settings object in `workspace/didChangeConfiguration` (but that's an issue on our side).

It's probably also fine to leave it; LSP is quite underspecified about how these requests interact. I heard that both are deprecated in favor of `workspace/configuration`..

Comments:

Patch:
diff --git a/pylsp/config/config.py b/pylsp/config/config.py
index b458069..454ee4b 100644
--- a/pylsp/config/config.py
+++ b/pylsp/config/config.py
@@ -98,6 +98,10 @@ class Config:
                 self._plugin_settings, plugin_conf
             )
 
+        self._plugin_settings = _utils.merge_dicts(
+            self._plugin_settings, self._init_opts.get("pylsp", {})
+        )
+
         self._update_disabled_plugins()
 
     @property


Test patch:
diff --git a/test/test_configuration.py b/test/test_configuration.py
new file mode 100644
index 0000000..91da421
--- /dev/null
+++ b/test/test_configuration.py
@@ -0,0 +1,53 @@
+# Copyright 2021- Python Language Server Contributors.
+
+from unittest.mock import patch
+
+from test.test_utils import send_initialize_request
+from test.test_notebook_document import wait_for_condition
+
+import pytest
+
+from pylsp import IS_WIN
+
+
+INITIALIZATION_OPTIONS = {
+    "pylsp": {
+        "plugins": {
+            "flake8": {"enabled": True},
+            "pycodestyle": {"enabled": False},
+            "pyflakes": {"enabled": False},
+        },
+    }
+}
+
+
+@pytest.mark.skipif(IS_WIN, reason="Flaky on Windows")
+def test_set_flake8_using_init_opts(client_server_pair):
+    client, server = client_server_pair
+    send_initialize_request(client, INITIALIZATION_OPTIONS)
+    for key, value in INITIALIZATION_OPTIONS["pylsp"]["plugins"].items():
+        assert server.workspace._config.settings().get("plugins").get(key).get(
+            "enabled"
+        ) == value.get("enabled")
+
+
+@pytest.mark.skipif(IS_WIN, reason="Flaky on Windows")
+def test_set_flake8_using_workspace_did_change_configuration(client_server_pair):
+    client, server = client_server_pair
+    send_initialize_request(client, None)
+    assert (
+        server.workspace._config.settings().get("plugins").get("flake8").get("enabled")
+        is False
+    )
+
+    with patch.object(server.workspace, "update_config") as mock_update_config:
+        client._endpoint.notify(
+            "workspace/didChangeConfiguration",
+            {"settings": INITIALIZATION_OPTIONS},
+        )
+        wait_for_condition(lambda: mock_update_config.call_count >= 1)
+
+        for key, value in INITIALIZATION_OPTIONS["pylsp"]["plugins"].items():
+            assert server.workspace._config.settings().get("plugins").get(key).get(
+                "enabled"
+            ) == value.get("enabled")
diff --git a/test/test_utils.py b/test/test_utils.py
index fb4a8b8..8b518d7 100644
--- a/test/test_utils.py
+++ b/test/test_utils.py
@@ -6,7 +6,7 @@ import os
 import sys
 from threading import Thread
 import time
-from typing import List
+from typing import Any, Dict, List
 from unittest import mock
 
 from flaky import flaky
@@ -62,12 +62,13 @@ def notebook_with_python_cells(cells: List[str]):
     }
 
 
-def send_initialize_request(client):
+def send_initialize_request(client, initialization_options: Dict[str, Any] = None):
     return client._endpoint.request(
         "initialize",
         {
             "processId": 1234,
             "rootPath": os.path.dirname(__file__),
+            "initializationOptions": initialization_options,
         },
     ).result(timeout=CALL_TIMEOUT_IN_SECONDS)