mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-12 14:15:14 +00:00
SolidLanguageServer: Allow to open file buffers lazily (without opening in LS)
Not passing the open request directly on to the LS can speed things up for cases where we don't necessarily need the file to be open in the LS at first (but do need to open the file and read its contents) Applied to request_document_symbols, where we check the cache based on the file contents but only need to open the file in the LS if there is a cache miss. Relates to #984
This commit is contained in:
1 parent
811e1981b5
commit
cefa197743
1 file changed
+83
-32
+83
-32
@@ -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:
|
||||
@@ -611,11 +657,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")
|
||||
@@ -625,56 +674,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:
|
||||
@@ -1130,7 +1180,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)
|
||||
@@ -1145,6 +1195,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:
|
||||
|
||||
Reference in new issue
Block a user