Merge pull request #985 from oraios/lazy-open-file-in-ls

SolidLanguageServer: Allow to open file buffers lazily (without opening in LS)
This commit is contained in:
Dominik Jain authored and GitHub committed 2026-02-02 20:10:09 +01:00
commit 4dab291c35
1 file changed
+83 -32
+83 -32
View File
@@ -64,7 +64,18 @@ class LSPFileBuffer:
This class is used to store the contents of an open LSP file in memory.
"""
def __init__(self, uri: str, contents: str, encoding: str, version: int, language_id: str, ref_count: int) -> None:
def __init__(
self,
uri: str,
contents: str,
encoding: str,
version: int,
language_id: str,
ref_count: int,
language_server: "SolidLanguageServer",
open_in_ls: bool = True,
) -> None:
self.language_server = language_server
self.uri = uri
self.contents = contents
self.version = version
@@ -72,6 +83,41 @@ class LSPFileBuffer:
self.ref_count = ref_count
self.encoding = encoding
self._content_hash: str | None = None
self._is_open_in_ls = False
if open_in_ls:
self._open_in_ls()
def _open_in_ls(self):
"""
Open the file in the language server if it is not already open.
"""
if self._is_open_in_ls:
return
self._is_open_in_ls = True
self.language_server.server.notify.did_open_text_document(
{
LSPConstants.TEXT_DOCUMENT: { # type: ignore
LSPConstants.URI: self.uri,
LSPConstants.LANGUAGE_ID: self.language_id,
LSPConstants.VERSION: 0,
LSPConstants.TEXT: self.contents,
}
}
)
def close(self):
if self._is_open_in_ls:
self.language_server.server.notify.did_close_text_document(
{
LSPConstants.TEXT_DOCUMENT: { # type: ignore
LSPConstants.URI: self.uri,
}
}
)
def ensure_open_in_ls(self) -> None:
"""Ensure that the file is opened in the language server."""
self._open_in_ls()
@property
def content_hash(self) -> str:
@@ -610,11 +656,14 @@ class SolidLanguageServer(ABC):
return self.language_id
@contextmanager
def open_file(self, relative_file_path: str) -> Iterator[LSPFileBuffer]:
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.
: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.
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.
"""
if not self.server_started:
log.error("open_file called before Language Server started")
@@ -624,56 +673,57 @@ class SolidLanguageServer(ABC):
uri = pathlib.Path(absolute_file_path).as_uri()
if uri in self.open_file_buffers:
assert self.open_file_buffers[uri].uri == uri
assert self.open_file_buffers[uri].ref_count >= 1
fb = self.open_file_buffers[uri]
assert fb.uri == uri
assert fb.ref_count >= 1
self.open_file_buffers[uri].ref_count += 1
yield self.open_file_buffers[uri]
self.open_file_buffers[uri].ref_count -= 1
fb.ref_count += 1
if open_in_ls:
fb.ensure_open_in_ls()
yield fb
fb.ref_count -= 1
else:
contents = FileUtils.read_file(absolute_file_path, self._encoding)
version = 0
language_id = self._get_language_id_for_file(relative_file_path)
self.open_file_buffers[uri] = LSPFileBuffer(
uri=uri, contents=contents, encoding=self._encoding, version=version, language_id=language_id, ref_count=1
fb = LSPFileBuffer(
uri=uri,
contents=contents,
encoding=self._encoding,
version=version,
language_id=language_id,
ref_count=1,
language_server=self,
open_in_ls=open_in_ls,
)
self.server.notify.did_open_text_document(
{
LSPConstants.TEXT_DOCUMENT: { # type: ignore
LSPConstants.URI: uri,
LSPConstants.LANGUAGE_ID: language_id,
LSPConstants.VERSION: 0,
LSPConstants.TEXT: contents,
}
}
)
yield self.open_file_buffers[uri]
self.open_file_buffers[uri].ref_count -= 1
self.open_file_buffers[uri] = fb
yield fb
fb.ref_count -= 1
if self.open_file_buffers[uri].ref_count == 0:
self.server.notify.did_close_text_document(
{
LSPConstants.TEXT_DOCUMENT: { # type: ignore
LSPConstants.URI: uri,
}
}
)
self.open_file_buffers[uri].close()
del self.open_file_buffers[uri]
@contextmanager
def _open_file_context(self, relative_file_path: str, file_buffer: LSPFileBuffer | None = None) -> Iterator[LSPFileBuffer]:
def _open_file_context(
self, relative_file_path: str, file_buffer: LSPFileBuffer | None = None, open_in_ls: bool = True
) -> Iterator[LSPFileBuffer]:
"""
Internal context manager to open a file, optionally reusing an existing file buffer.
:param relative_file_path: the relative path of the file to open.
:param file_buffer: an optional existing file buffer to reuse.
:param open_in_ls: whether to open the file in the language server, sending the didOpen notification.
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.
"""
if file_buffer is not None:
if open_in_ls:
file_buffer.ensure_open_in_ls()
yield file_buffer
else:
with self.open_file(relative_file_path) as fb:
with self.open_file(relative_file_path, open_in_ls=open_in_ls) as fb:
yield fb
def insert_text_at_position(self, relative_file_path: str, line: int, column: int, text_to_be_inserted: str) -> ls_types.Position:
@@ -1128,7 +1178,7 @@ class SolidLanguageServer(ABC):
where the parent attribute will be the file symbol which in turn may have a package symbol as parent.
If you need a symbol tree that contains file symbols as well, you should use `request_full_symbol_tree` instead.
"""
with self._open_file_context(relative_file_path, file_buffer) as file_data:
with self._open_file_context(relative_file_path, file_buffer, open_in_ls=False) as file_data:
# check if the desired result is cached
cache_key = relative_file_path
file_hash_and_result = self._document_symbols_cache.get(cache_key)
@@ -1143,6 +1193,7 @@ class SolidLanguageServer(ABC):
log.debug("No cache hit for document symbols in %s", relative_file_path)
# no cached result: request the root symbols from the language server
file_data.ensure_open_in_ls()
root_symbols = self._request_document_symbols(relative_file_path, file_data)
if root_symbols is None: