From 70000e3326ac36cbbbe1d9d7b0d779096feb2e3f Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Fri, 15 Dec 2023 21:07:53 +0000 Subject: [PATCH] Only parse the initial file the first time it's opened When the language_server_completer is first constructed, it's usually a FileReadyToParse callback. But at that time, we can't actually parse anything because we haven't done the initialization exchange with the server, so we queue this up for later. The way this was implemenented was to register a "on file ready to parse" callback to update the server with the file contents at the time the completer was constructed. In practice, as the completer is usually constructed via a file ready to pase event, this actually happens immediately and we then shunt the request into "OnInitializeComplete" handlers, which are fired when the initialize exchange happens. Why don't we just put something directly in OnInitializeComplete handlers? Well because in the constructor, we don't have the request_data - we only get that in the OnFileReadyToParse callback, so we have this jank. WCGW? As a result of this dance, we actually introduced a subtle error. OnFileReadyToParse handlers are called _every time_ we have a file parse event, and - we never _remove_ this "parse the file contents as they were at the time of initialization" request from the "file ready to parse handlers" list. So every time we has a file parse event, we update the server with _stale_ data, then immediately correct it! In practice, this isn't all that noticable, but it is braindead. Simple solution is to add a "once" flag to the handler creating oneshot handlers, and clear such handlers after firing them (or converting them to initialize handlers). Jank to fix jank. It might be better to find a way to just remove these handler "lists" altogether, as there is only one other place in the codebase where one is registered, and we could always just pass request_data to constructor if we have it... --- .../language_server_completer.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/ycmd/completers/language_server/language_server_completer.py b/ycmd/completers/language_server/language_server_completer.py index a2796e81ce..ed376b3190 100644 --- a/ycmd/completers/language_server/language_server_completer.py +++ b/ycmd/completers/language_server/language_server_completer.py @@ -1005,7 +1005,8 @@ def __init__( self, user_options, connection_type = 'stdio' ): self._on_file_ready_to_parse_handlers = [] self.RegisterOnFileReadyToParse( lambda self, request_data: - self._UpdateServerWithFileContents( request_data ) + self._UpdateServerWithFileContents( request_data ), + True # once ) self._signature_help_disabled = user_options[ 'disable_signature_help' ] @@ -1898,6 +1899,12 @@ def OnFileReadyToParse( self, request_data ): if not self.ServerIsHealthy(): return + def ClearOneshotHandlers(): + self._on_file_ready_to_parse_handlers = [ + ( handler, once ) for handler, once + in self._on_file_ready_to_parse_handlers if not once + ] + # If we haven't finished initializing yet, we need to queue up all functions # registered on the FileReadyToParse event and in particular # _UpdateServerWithFileContents in reverse order of registration. This @@ -1905,13 +1912,15 @@ def OnFileReadyToParse( self, request_data ): # messages. This is important because server start up can be quite slow and # we must not block the user, while we must keep the server synchronized. if not self._initialize_event.is_set(): - for handler in reversed( self._on_file_ready_to_parse_handlers ): + for handler, _ in reversed( self._on_file_ready_to_parse_handlers ): self._OnInitializeComplete( partial( handler, request_data = request_data ) ) + ClearOneshotHandlers() return - for handler in reversed( self._on_file_ready_to_parse_handlers ): + for handler, _ in reversed( self._on_file_ready_to_parse_handlers ): handler( self, request_data ) + ClearOneshotHandlers() # Return the latest diagnostics that we have received. # @@ -2480,8 +2489,8 @@ def _OnInitializeComplete( self, handler ): self._on_initialize_complete_handlers.append( handler ) - def RegisterOnFileReadyToParse( self, handler ): - self._on_file_ready_to_parse_handlers.append( handler ) + def RegisterOnFileReadyToParse( self, handler, once=False ): + self._on_file_ready_to_parse_handlers.append( ( handler, once ) ) def GetHoverResponse( self, request_data ):