Add support for workspace/didChangeConfiguration#2862
Add support for workspace/didChangeConfiguration#2862jwortmann wants to merge 13 commits intosublimelsp:mainfrom
Conversation
✅ Deploy Preview for sublime-lsp ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
I did not look at the changes yet but I have a question. Some packages like for example LSP-pyright implement a custom configuration setting like Does this change affect that? Will we need to update those packages and move the setting to the root of the settings, for example? |
I know, and I believe I have written somewhere before that it is a bad idea and helper packages shouldn't mix custom package settings with server settings. So I would consider that as a bug in LSP-pyright. In LSP-julia I have ensured that custom package settings don't mix with server settings: https://github.com/sublimelsp/LSP-julia/blob/4751ae10ab015451292c11a8b1e9cc65594c8fac/LSP-julia.sublime-settings#L2-L9
With this PR, servers are restarted if the ClientConfig does not equal (
Servers that opt-in to didChangeConfiguration notifications would get a notification and then should pull the configuration. If there is no change to the settings that are known by the server, in theory nothing should happen. |
It was required to do that that way before because otherwise you wouldn't be able to do project-specific override.
I would think that restarting on a change of those would be more often correct given what those settings are used for currently (setting server path or toggling server manage). In your lsp-julia example one would require restart, the other perhaps not? The
That's understandable but I'm obviously talking about cases like the one from LSP-pyright in which case we should do something. Can/should we handle those cases gracefully somehow? |
| def __eq__(self, other: object) -> bool: | ||
| if not isinstance(other, ClientConfig): | ||
| return False | ||
| for k, v in self.__dict__.items(): | ||
| if not k.startswith("_") and v != getattr(other, k): | ||
| return False | ||
| return True | ||
| return self.name == other.name and self._all_settings == other._all_settings |
There was a problem hiding this comment.
Should we actually have a dedicated method for comparing two configs? I feel like overriding the __eq__ implementation for that purpose is not right. Technically __eq__ should answer the question whether two objects are identical but we're using it more for "did settings that cause server restart change".
Also it's hard to search for places that rely on this check to verify if we didn't break something by changing this implementation.
There was a problem hiding this comment.
Yeah maybe. I also had the same thoughts before. But I don't think there is any other code that would compare two ClientConfigs, and this already had some custom implementation (which excludes properties starting with _ from being considered when compared for equality, which for example already includes the "enabled", so I guess we could do the same with "settings"). There is also no comment in the source code that explains the exact purpose of the current custom __eq__ implementation. So I thought we could just use __eq__ when checking for "server-restart-equality"...
|
I have noticed one strange bug with this PR that makes helper packages often not work correctly. I really wonder what the purpose of this code in SettingsRegistration is: Lines 179 to 188 in b2cd51d When helper plugins are registered, we create a ClientConfig for each of them. This included a SettingsRegistration object, which is stored as part of the ClientConfig. But when it is garbage collected, the change listener is removed, so new changes in the sublime-settings file don't trigger the callback function anymore. And when something in the settings file changes, we have to create a new ClientConfig and replace the old one that was stored in the Lines 83 to 85 in b2cd51d I don't think I would have written such complicated code with all these self references and weak references that rely on things not being garbage collected for change listeners to work. Why not just attach a single change listener function when a plugin gets registered, and keep that around forever (until plugin unregistered)? In general when writing the code in this PR I saw so much really weird and complicated code with all kind of self references and custom change listener abstractions in abstract base classes which even result in reference cycles between objects. And all these abstract base class interfaces make "Goto Definition" not work in the LSP code base, because Pyright always jumps to the abstract base class instead of to the implementation (I noticed that same annoyance many times before with the SessionBufferProtocol and SessionViewProtocol classes). For example the following chain of functions is called when the "clients" in LSP.sublime-settings gets modified:
This jumps from Edit: And another point why I think relying for things trigger on garbage collection is bad: |
It might be wrong but also
Makes sense in theory but I would have to dig into the code to understand things better.
Yes, but there is "Go to implementation" command to jump from abstract class to implementations.
That specific case of SettingsRegistration didn't rely on GC originally. It was done like that to workaround an issue with ST freezing - #2822 |
Okay, I figured out that this is unrelated to this PR and also happens on main (can be tested by manually setting Line 985 in b2cd51d self.settings and self.settings_path properties (I think). And then the new copy doesn't have any change listener function attached.
|
Good catch. I didn't intend to copy non-plain objects like that and I didn't do it in Created #2867 |
"FIXME" is a standardized pattern for TODO-comments, see e.g. https://www.jetbrains.com/help/pycharm/using-todo.html
This PR implements the second point in #2800
LanguageServers.sublime-settingsfor the manual configurations. This allows for a cleaner separation of LSP settings and server configs, so we can attach different change listeners to these files.LSP.sublime-settingsstill work, but I've marked it as deprecated in the JSON schema. If there is a configuration with the same name also inLanguageServers.sublime-settings, then that will take precedence.workspace/didChangeConfigurationto notify the server when settings were changed. This is only implemented for configs from the newLanguageServers.sublime-settingsfile and for external configs (from helper packages), but not for the "clients" in the LSP settings. Servers can opt-in to change notifications via dynamic registration (see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_configuration). This allows to dynamically change settings while the server is running, without forcing a restart of the server (restart can take a long time for example if the server is implemented in a JIT-compiled language, or if project indexing is slow).workspace/didChangeConfigurationcan be tested for example with JETLS. (That server shows the updated settings after a new configuration request in awindow/showMessagenotification, which we log to the console.)workspace/didChangeConfiguration, nothing happens until the server initiates a newworkspace/configurationrequest by itself (which might never happen), or until the server is manually restarted via LSP: Restart Server. This could be unexpected to users, so I wonder if we should handle that case also in LSP directly. This happens for example with Pyright.Is the change with the new config file too intrusive? IMO it's nice to have a clean separation between LSP settings and server configs, and I always disliked that server configurations were named "clients" in the settings file.