Fix: Change the semantics of file opening (open_file) in the language server

Instead of sending only the didOpen notification if the file is not already open,
the method now ensures that the language server has the (current) contents of the file,
sending the didChange notification in cases where the file is already open but has changed
on disk. This better reflects the actual intention of calling the method.

This affects the Vue and Svelte language servers, which unlike all other server implementations,
kept files open, which could result in the servers reporting stale information in the
absence of update notifications (external file changes).
This commit is contained in:
Dominik Jain committed 2026-08-02 13:11:04 +02:00
1 parent e5c86a3422
commit 9eb4df2e7b
2 files changed
+49 -14

No files matched your search

+5
View File
@@ -40,6 +40,11 @@ Status of the `main` branch. Changes prior to the next official version change w
returned the keyword's own docs instead of the module's #925
- Fix: `LSPFileBuffer`: a stale content hash could be returned if files are kept open
and file contents were not read before trying to retrieve the hash value
- Fix: Change semantics of file opening (`open_file`) in the language server from "open file (if not already open)"
to "ensure that the language server has the (current) contents of the file" (by sending `textDocument/didOpen`
or `textDocument/didChange`), as this is always the intention of calling the method.
If files were kept open in the language server (which the Svelte and Vue language servers did),
the language server was not necessarily informed about updated contents.
* JetBrains:
- `jet_brains_find_symbol`: Disallow wildcard-only search, delegating to overview tool if request is for file
+44 -14
View File
@@ -107,6 +107,7 @@ class LSPFileBuffer:
self.language_server = language_server
self.uri = uri
self._read_file_modified_date: float | None = None
self._read_file_modified_date_passed_to_ls: float | None = None
self._contents: str | None = None
self.version = version
self.language_id = language_id
@@ -120,20 +121,40 @@ class LSPFileBuffer:
def _open_in_ls(self) -> None:
"""
Open the file in the language server if it is not already open.
If it is already open, make sure the language server has the latest contents of the file.
"""
if self._is_open_in_ls:
return
self._is_open_in_ls = True
self.language_server.server.notify.did_open_text_document(
{ # ty: ignore[invalid-argument-type] # dict built from LSPConstants keys; shape matches the TypedDict
LSPConstants.TEXT_DOCUMENT: {
LSPConstants.URI: self.uri,
LSPConstants.LANGUAGE_ID: self.language_id,
LSPConstants.VERSION: 0,
LSPConstants.TEXT: self.contents,
if not self._is_open_in_ls:
self._is_open_in_ls = True
current_contents = self.contents
self._read_file_modified_date_passed_to_ls = self._read_file_modified_date
self.language_server.server.notify.did_open_text_document(
{ # ty: ignore[invalid-argument-type] # dict built from LSPConstants keys; shape matches the TypedDict
LSPConstants.TEXT_DOCUMENT: {
LSPConstants.URI: self.uri,
LSPConstants.LANGUAGE_ID: self.language_id,
LSPConstants.VERSION: self.version,
LSPConstants.TEXT: current_contents,
}
}
}
)
)
else:
# file already open: check if contents have changed and notify if so
current_contents = self.contents
if self._read_file_modified_date != self._read_file_modified_date_passed_to_ls:
self._read_file_modified_date_passed_to_ls = self._read_file_modified_date
self.language_server.server.notify.did_change_text_document(
{ # ty: ignore[invalid-argument-type] # dict built from LSPConstants keys; shape matches the TypedDict
LSPConstants.TEXT_DOCUMENT: {
LSPConstants.URI: self.uri,
LSPConstants.VERSION: self.version,
},
LSPConstants.CONTENT_CHANGES: [
{
LSPConstants.TEXT: current_contents,
}
],
}
)
def close(self) -> None:
if self._is_open_in_ls:
@@ -146,7 +167,11 @@ class LSPFileBuffer:
)
def ensure_open_in_ls(self) -> None:
"""Ensure that the file is opened in the language server."""
"""
Ensure that the file is opened in the language server (or, if it is already open,
that the language server is made aware of the file's updated contents in case it
has changed on disk).
"""
self._open_in_ls()
def _invalidate_cached_data(self, mtime: float | None = None) -> float | None:
@@ -1254,10 +1279,15 @@ class SolidLanguageServer(ABC):
@contextmanager
def open_file(self, relative_file_path: str, open_in_ls: bool = True) -> Iterator[LSPFileBuffer]:
"""
Open a file in the Language Server. This is required before making any requests to the Language Server.
Opens a file.
Note: Opening a file in the language server is typically a precondition for further requests
pertaining to the respective file.
:param relative_file_path: The relative path of the file to open.
:param open_in_ls: whether to open the file in the language server, sending the didOpen notification.
If the file is already open but file contents has changed since the original notification,
an update notification is sent instead.
Set this to False to read the local file buffer without notifying the LS; the file can
be opened in the LS later by calling the `ensure_open_in_ls` method on the returned LSPFileBuffer.
"""