mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 04:15:11 +00:00
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:
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:
|
||||
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user