mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 04:15:11 +00:00
Merge remote-tracking branch 'origin/name-path-overloads' into name-path-description
Conflicts: src/serena/tools/symbol_tools.py
This commit is contained in:
commit
fc7a0938e2
48 files changed
+612
-406
No files matched your search
+2
-1
@@ -70,7 +70,8 @@ dev = [
|
||||
"sphinx_rtd_theme>=0.5.1",
|
||||
"sphinx-toolbox==3.7.0",
|
||||
"jupyter-book>=1,<2",
|
||||
"nbsphinx"
|
||||
"nbsphinx",
|
||||
"pyinstrument",
|
||||
]
|
||||
agno = ["agno>=2.2.1", "sqlalchemy>=2.0.40"]
|
||||
google = ["google-genai>=1.8.0"]
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import cProfile
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
from sensai.util import logging
|
||||
from sensai.util.logging import LogTime
|
||||
from sensai.util.profiling import profiled
|
||||
|
||||
from serena.agent import SerenaAgent
|
||||
from serena.config.serena_config import SerenaConfig
|
||||
from serena.tools import FindSymbolTool
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.configure()
|
||||
|
||||
# The profiler to use:
|
||||
# Use pyinstrument for hierarchical profiling output
|
||||
# Use cProfile to determine which functions take the most time overall (and use snakeviz to visualize)
|
||||
profiler: Literal["pyinstrument", "cprofile"] = "cprofile"
|
||||
|
||||
project_path = Path(__file__).parent.parent # Serena root
|
||||
|
||||
serena_config = SerenaConfig.from_config_file()
|
||||
serena_config.log_level = logging.INFO
|
||||
serena_config.jetbrains = False
|
||||
serena_config.gui_log_window_enabled = False
|
||||
serena_config.web_dashboard = False
|
||||
|
||||
agent = SerenaAgent(str(project_path), serena_config=serena_config)
|
||||
|
||||
# wait for language server to be ready
|
||||
agent.execute_task(lambda: log.info("Language server is ready."))
|
||||
|
||||
def tool_call():
|
||||
"""This is the function we want to profile."""
|
||||
# NOTE: We use apply (not apply_ex) to run the tool call directly on the main thread
|
||||
with LogTime("Tool call"):
|
||||
result = agent.get_tool(FindSymbolTool).apply(name_path="DQN")
|
||||
log.info("Tool result:\n%s", result)
|
||||
|
||||
if profiler == "pyinstrument":
|
||||
|
||||
@profiled(log_to_file=True)
|
||||
def profiled_tool_call():
|
||||
tool_call()
|
||||
|
||||
profiled_tool_call()
|
||||
|
||||
elif profiler == "cprofile":
|
||||
cProfile.run("tool_call()", "tool_call.pstat")
|
||||
+5
-6
@@ -478,8 +478,7 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
files_failed = []
|
||||
for i, f in enumerate(tqdm(files, desc="Indexing")):
|
||||
try:
|
||||
ls.request_document_symbols(f, include_body=False)
|
||||
ls.request_document_symbols(f, include_body=True)
|
||||
ls.request_document_symbols(f)
|
||||
except Exception as e:
|
||||
log.error(f"Failed to index {f}, continuing.")
|
||||
collected_exceptions.append(e)
|
||||
@@ -487,7 +486,7 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
if (i + 1) % 10 == 0:
|
||||
ls.save_cache()
|
||||
ls.save_cache()
|
||||
click.echo(f"Symbols saved to {ls.cache_path}")
|
||||
click.echo(f"Symbols saved to {ls.cache_dir}")
|
||||
|
||||
if len(files_failed) > 0:
|
||||
os.makedirs(os.path.dirname(log_file), exist_ok=True)
|
||||
@@ -538,14 +537,14 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
try:
|
||||
for ls in ls_mgr.iter_language_servers():
|
||||
click.echo(f"Indexing for language {ls.language.value} …")
|
||||
symbols, _ = ls.request_document_symbols(file, include_body=False)
|
||||
ls.request_document_symbols(file, include_body=True)
|
||||
document_symbols = ls.request_document_symbols(file)
|
||||
symbols, _ = document_symbols.get_all_symbols_and_roots()
|
||||
if verbose:
|
||||
click.echo(f"Symbols in file '{file}':")
|
||||
for symbol in symbols:
|
||||
click.echo(f" - {symbol['name']} at line {symbol['selectionRange']['start']['line']} of kind {symbol['kind']}")
|
||||
ls.save_cache()
|
||||
click.echo(f"Successfully indexed file '{file}', {len(symbols)} symbols saved to {ls.cache_path}.")
|
||||
click.echo(f"Successfully indexed file '{file}', {len(symbols)} symbols saved to cache in {ls.cache_dir}.")
|
||||
finally:
|
||||
ls_mgr.stop_all()
|
||||
|
||||
|
||||
+3
-10
@@ -491,7 +491,6 @@ class LanguageServerSymbolRetriever:
|
||||
def find_by_name(
|
||||
self,
|
||||
name_path: str,
|
||||
include_body: bool = False,
|
||||
include_kinds: Sequence[SymbolKind] | None = None,
|
||||
exclude_kinds: Sequence[SymbolKind] | None = None,
|
||||
substring_matching: bool = False,
|
||||
@@ -504,7 +503,7 @@ class LanguageServerSymbolRetriever:
|
||||
"""
|
||||
symbols: list[LanguageServerSymbol] = []
|
||||
for lang_server in self._ls_manager.iter_language_servers():
|
||||
symbol_roots = lang_server.request_full_symbol_tree(within_relative_path=within_relative_path, include_body=include_body)
|
||||
symbol_roots = lang_server.request_full_symbol_tree(within_relative_path=within_relative_path)
|
||||
for root in symbol_roots:
|
||||
symbols.extend(
|
||||
LanguageServerSymbol(root).find(
|
||||
@@ -513,18 +512,12 @@ class LanguageServerSymbolRetriever:
|
||||
)
|
||||
return symbols
|
||||
|
||||
def get_document_symbols(self, relative_path: str) -> list[LanguageServerSymbol]:
|
||||
lang_server = self.get_language_server(relative_path)
|
||||
symbol_dicts, _roots = lang_server.request_document_symbols(relative_path, include_body=False)
|
||||
symbols = [LanguageServerSymbol(s) for s in symbol_dicts]
|
||||
return symbols
|
||||
|
||||
def find_by_location(self, location: LanguageServerSymbolLocation) -> LanguageServerSymbol | None:
|
||||
if location.relative_path is None:
|
||||
return None
|
||||
lang_server = self.get_language_server(location.relative_path)
|
||||
symbol_dicts, _roots = lang_server.request_document_symbols(location.relative_path, include_body=False)
|
||||
for symbol_dict in symbol_dicts:
|
||||
document_symbols = lang_server.request_document_symbols(location.relative_path)
|
||||
for symbol_dict in document_symbols.iter_symbols():
|
||||
symbol = LanguageServerSymbol(symbol_dict)
|
||||
if symbol.location == location:
|
||||
return symbol
|
||||
|
||||
@@ -139,7 +139,6 @@ class FindSymbolTool(Tool, ToolMarkerSymbolicRead):
|
||||
symbol_retriever = self.create_language_server_symbol_retriever()
|
||||
symbols = symbol_retriever.find_by_name(
|
||||
name_path_pattern,
|
||||
include_body=include_body,
|
||||
include_kinds=parsed_include_kinds,
|
||||
exclude_kinds=parsed_exclude_kinds,
|
||||
substring_matching=substring_matching,
|
||||
|
||||
@@ -680,7 +680,7 @@ class ALLanguageServer(SolidLanguageServer):
|
||||
return super().is_ignored_dirname(dirname) or dirname in al_ignore_dirs
|
||||
|
||||
@override
|
||||
def request_full_symbol_tree(self, within_relative_path: str | None = None, include_body: bool = False) -> list[dict]:
|
||||
def request_full_symbol_tree(self, within_relative_path: str | None = None) -> list[dict]:
|
||||
"""
|
||||
Override to handle AL's requirement of opening files before requesting symbols.
|
||||
|
||||
@@ -713,7 +713,7 @@ class ALLanguageServer(SolidLanguageServer):
|
||||
|
||||
if os.path.isfile(within_abs_path):
|
||||
# Single file case - use parent class implementation
|
||||
_, root_nodes = self.request_document_symbols(within_relative_path, include_body=include_body)
|
||||
root_nodes = self.request_document_symbols(within_relative_path).root_symbols
|
||||
return root_nodes
|
||||
|
||||
# Directory case - scan within this directory
|
||||
@@ -755,7 +755,7 @@ class ALLanguageServer(SolidLanguageServer):
|
||||
try:
|
||||
# Use our overridden request_document_symbols which handles opening
|
||||
self.logger.log(f"AL: Getting symbols for {relative_path}", logging.DEBUG)
|
||||
all_syms, root_syms = self.request_document_symbols(relative_path, include_body=include_body)
|
||||
all_syms, root_syms = self.request_document_symbols(relative_path).get_all_symbols_and_roots()
|
||||
|
||||
if root_syms:
|
||||
# Create a file-level symbol containing the document symbols
|
||||
|
||||
@@ -9,9 +9,8 @@ import pathlib
|
||||
import shutil
|
||||
import threading
|
||||
|
||||
from solidlsp import ls_types
|
||||
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls import DocumentSymbols, LSPFileBuffer, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
@@ -194,37 +193,24 @@ class BashLanguageServer(SolidLanguageServer):
|
||||
else:
|
||||
self.logger.log("Bash server initialization complete", logging.INFO)
|
||||
|
||||
def request_document_symbols(
|
||||
self, relative_file_path: str, include_body: bool = False
|
||||
) -> tuple[list[ls_types.UnifiedSymbolInformation], list[ls_types.UnifiedSymbolInformation]]:
|
||||
"""
|
||||
Request document symbols from bash-language-server via LSP.
|
||||
def request_document_symbols(self, relative_file_path: str, file_buffer: LSPFileBuffer | None = None) -> DocumentSymbols:
|
||||
# Uses the standard LSP documentSymbol request which provides reliable function detection
|
||||
# for all bash function syntaxes including:
|
||||
# - function name() { ... } (with function keyword)
|
||||
# - name() { ... } (traditional syntax)
|
||||
# - Functions with various indentation levels
|
||||
# - Functions with comments before/after/inside
|
||||
|
||||
Uses the standard LSP documentSymbol request which provides reliable function detection
|
||||
for all bash function syntaxes including:
|
||||
- function name() { ... } (with function keyword)
|
||||
- name() { ... } (traditional syntax)
|
||||
- Functions with various indentation levels
|
||||
- Functions with comments before/after/inside
|
||||
|
||||
Args:
|
||||
relative_file_path: Path to the bash file relative to repository root
|
||||
include_body: Whether to include function bodies in symbol information
|
||||
|
||||
Returns:
|
||||
Tuple of (all_symbols, root_symbols) detected by the LSP server
|
||||
|
||||
"""
|
||||
self.logger.log(f"Requesting document symbols via LSP for {relative_file_path}", logging.DEBUG)
|
||||
|
||||
# Use the standard LSP approach - bash-language-server handles all function syntaxes correctly
|
||||
all_symbols, root_symbols = super().request_document_symbols(relative_file_path, include_body)
|
||||
document_symbols = super().request_document_symbols(relative_file_path, file_buffer=file_buffer)
|
||||
|
||||
# Log detection results for debugging
|
||||
functions = [s for s in all_symbols if s.get("kind") == 12]
|
||||
functions = [s for s in document_symbols.iter_symbols() if s.get("kind") == 12]
|
||||
self.logger.log(
|
||||
f"LSP function detection for {relative_file_path}: Found {len(functions)} functions",
|
||||
logging.INFO,
|
||||
)
|
||||
|
||||
return all_symbols, root_symbols
|
||||
return document_symbols
|
||||
@@ -789,8 +789,8 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
# TODO: Add comments about why we wait here, and how this can be optimized
|
||||
self.service_ready_event.wait()
|
||||
|
||||
def _request_document_symbols(self, relative_file_path: str, include_body: bool = False) -> list[GenericDocumentSymbol]:
|
||||
result = super()._request_document_symbols(relative_file_path, include_body)
|
||||
def _request_document_symbols(self, relative_file_path: str) -> list[GenericDocumentSymbol]:
|
||||
result = super()._request_document_symbols(relative_file_path)
|
||||
|
||||
# JDTLS sometimes returns symbol names with type information to handle overloads,
|
||||
# e.g. "myMethod(int) <T>", but we want overloads to be handled via overload_idx,
|
||||
|
||||
@@ -12,7 +12,7 @@ import threading
|
||||
from overrides import override
|
||||
|
||||
from solidlsp import ls_types
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls import DocumentSymbols, LSPFileBuffer, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
@@ -138,24 +138,20 @@ class FortranLanguageServer(SolidLanguageServer):
|
||||
return symbol
|
||||
|
||||
@override
|
||||
def request_document_symbols(
|
||||
self, relative_file_path: str, include_body: bool = False
|
||||
) -> tuple[list[ls_types.UnifiedSymbolInformation], list[ls_types.UnifiedSymbolInformation]]:
|
||||
"""
|
||||
Override to fix fortls's incorrect selectionRange bug.
|
||||
def request_document_symbols(self, relative_file_path: str, file_buffer: LSPFileBuffer | None = None) -> DocumentSymbols:
|
||||
# Override to fix fortls's incorrect selectionRange bug.
|
||||
#
|
||||
# fortls returns selectionRange pointing to line start (character 0) instead of the
|
||||
# identifier name position. This breaks MCP server features that rely on exact positions.
|
||||
#
|
||||
# This override:
|
||||
# 1. Gets symbols from fortls via parent implementation
|
||||
# 2. Parses each symbol's line to find the correct identifier position
|
||||
# 3. Fixes selectionRange for all symbols recursively
|
||||
# 4. Returns corrected symbols
|
||||
|
||||
fortls returns selectionRange pointing to line start (character 0) instead of the
|
||||
identifier name position. This breaks MCP server features that rely on exact positions.
|
||||
|
||||
This override:
|
||||
1. Gets symbols from fortls via parent implementation
|
||||
2. Parses each symbol's line to find the correct identifier position
|
||||
3. Fixes selectionRange for all symbols recursively
|
||||
4. Returns corrected symbols
|
||||
|
||||
"""
|
||||
# Get symbols from fortls (with incorrect selectionRange)
|
||||
all_symbols, root_symbols = super().request_document_symbols(relative_file_path, include_body)
|
||||
document_symbols = super().request_document_symbols(relative_file_path, file_buffer=file_buffer)
|
||||
|
||||
# Get file content for parsing
|
||||
with self.open_file(relative_file_path) as file_data:
|
||||
@@ -173,10 +169,9 @@ class FortranLanguageServer(SolidLanguageServer):
|
||||
return fixed
|
||||
|
||||
# Apply fix to all symbols
|
||||
fixed_all_symbols = [fix_symbol_and_children(sym) for sym in all_symbols]
|
||||
fixed_root_symbols = [fix_symbol_and_children(sym) for sym in root_symbols]
|
||||
fixed_root_symbols = [fix_symbol_and_children(sym) for sym in document_symbols.root_symbols]
|
||||
|
||||
return fixed_all_symbols, fixed_root_symbols
|
||||
return DocumentSymbols(fixed_root_symbols)
|
||||
|
||||
@staticmethod
|
||||
def _check_fortls_installation():
|
||||
|
||||
@@ -16,7 +16,7 @@ from pathlib import Path
|
||||
from overrides import override
|
||||
|
||||
from solidlsp import ls_types
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls import DocumentSymbols, LSPFileBuffer, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
@@ -70,17 +70,13 @@ class NixLanguageServer(SolidLanguageServer):
|
||||
return symbol
|
||||
|
||||
@override
|
||||
def request_document_symbols(
|
||||
self, relative_file_path: str, include_body: bool = False
|
||||
) -> tuple[list[ls_types.UnifiedSymbolInformation], list[ls_types.UnifiedSymbolInformation]]:
|
||||
"""
|
||||
Override to extend Nix symbol ranges to include trailing semicolons.
|
||||
def request_document_symbols(self, relative_file_path: str, file_buffer: LSPFileBuffer | None = None) -> DocumentSymbols:
|
||||
# Override to extend Nix symbol ranges to include trailing semicolons.
|
||||
# nixd provides expression-level ranges (excluding semicolons) but serena needs
|
||||
# statement-level ranges (including semicolons) for proper symbol replacement.
|
||||
|
||||
nixd provides expression-level ranges (excluding semicolons) but serena needs
|
||||
statement-level ranges (including semicolons) for proper symbol replacement.
|
||||
"""
|
||||
# Get symbols from parent implementation
|
||||
all_symbols, root_symbols = super().request_document_symbols(relative_file_path, include_body)
|
||||
document_symbols = super().request_document_symbols(relative_file_path, file_buffer=file_buffer)
|
||||
|
||||
# Get file content for range extension
|
||||
file_content = self.language_server.retrieve_full_file_content(relative_file_path)
|
||||
@@ -97,10 +93,9 @@ class NixLanguageServer(SolidLanguageServer):
|
||||
return extended
|
||||
|
||||
# Apply range extension to all symbols
|
||||
extended_all_symbols = [extend_symbol_and_children(sym) for sym in all_symbols]
|
||||
extended_root_symbols = [extend_symbol_and_children(sym) for sym in root_symbols]
|
||||
extended_root_symbols = [extend_symbol_and_children(sym) for sym in document_symbols.root_symbols]
|
||||
|
||||
return extended_all_symbols, extended_root_symbols
|
||||
return DocumentSymbols(extended_root_symbols)
|
||||
|
||||
@override
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
|
||||
+377
-214
@@ -17,6 +17,7 @@ from time import sleep
|
||||
from typing import Self, Union, cast
|
||||
|
||||
import pathspec
|
||||
from sensai.util.pickle import getstate, load_pickle
|
||||
|
||||
from serena.text_utils import MatchedConsecutiveLines
|
||||
from serena.util.file_system import match_path
|
||||
@@ -40,6 +41,7 @@ from solidlsp.settings import SolidLSPSettings
|
||||
from solidlsp.util.cache import load_cache, save_cache
|
||||
|
||||
GenericDocumentSymbol = Union[LSPTypes.DocumentSymbol, LSPTypes.SymbolInformation, ls_types.UnifiedSymbolInformation]
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclasses.dataclass(kw_only=True)
|
||||
@@ -77,6 +79,50 @@ class LSPFileBuffer:
|
||||
def __post_init__(self):
|
||||
self.content_hash = hashlib.md5(self.contents.encode("utf-8")).hexdigest()
|
||||
|
||||
def split_lines(self) -> list[str]:
|
||||
"""Splits the contents of the file into lines."""
|
||||
return self.contents.split("\n")
|
||||
|
||||
|
||||
class DocumentSymbols:
|
||||
# IMPORTANT: Instances of this class are persisted in the high-level document symbol cache
|
||||
|
||||
def __init__(self, root_symbols: list[ls_types.UnifiedSymbolInformation]):
|
||||
self.root_symbols = root_symbols
|
||||
self._all_symbols: list[ls_types.UnifiedSymbolInformation] | None = None
|
||||
|
||||
def __getstate__(self):
|
||||
return getstate(DocumentSymbols, self, transient_properties=["_all_symbols"])
|
||||
|
||||
def iter_symbols(self) -> Iterator[ls_types.UnifiedSymbolInformation]:
|
||||
"""
|
||||
Iterate over all symbols in the document symbol tree.
|
||||
Yields symbols in a depth-first manner.
|
||||
"""
|
||||
if self._all_symbols is not None:
|
||||
yield from self._all_symbols
|
||||
return
|
||||
|
||||
def traverse(s: ls_types.UnifiedSymbolInformation):
|
||||
yield s
|
||||
for child in s.get("children", []):
|
||||
yield from traverse(child)
|
||||
|
||||
for root_symbol in self.root_symbols:
|
||||
yield from traverse(root_symbol)
|
||||
|
||||
def get_all_symbols_and_roots(self) -> tuple[list[ls_types.UnifiedSymbolInformation], list[ls_types.UnifiedSymbolInformation]]:
|
||||
"""
|
||||
This function returns all symbols in the document as a flat list and the root symbols.
|
||||
It exists to facilitate migration from previous versions, where this was the return interface of
|
||||
the LS method that obtained document symbols.
|
||||
|
||||
:return: A tuple containing a list of all symbols in the document and a list of root symbols.
|
||||
"""
|
||||
if self._all_symbols is None:
|
||||
self._all_symbols = list(self.iter_symbols())
|
||||
return self._all_symbols, self.root_symbols
|
||||
|
||||
|
||||
class SolidLanguageServer(ABC):
|
||||
"""
|
||||
@@ -85,7 +131,17 @@ class SolidLanguageServer(ABC):
|
||||
"""
|
||||
|
||||
CACHE_FOLDER_NAME = "cache"
|
||||
CACHE_VERSION = 2
|
||||
RAW_DOCUMENT_SYMBOLS_CACHE_VERSION = 1
|
||||
"""
|
||||
global version identifier for raw symbol caches; an LS-specific version is defined separately and combined with this.
|
||||
This should be incremented whenever there is a change in the way raw document symbols are stored.
|
||||
If the result of a language server changes in a way that affects the raw document symbols,
|
||||
the LS-specific version should be incremented instead.
|
||||
"""
|
||||
RAW_DOCUMENT_SYMBOL_CACHE_FILENAME = "raw_document_symbols.pkl"
|
||||
RAW_DOCUMENT_SYMBOL_CACHE_FILENAME_LEGACY_FALLBACK = "document_symbols_cache_v23-06-25.pkl"
|
||||
DOCUMENT_SYMBOL_CACHE_VERSION = 3
|
||||
DOCUMENT_SYMBOL_CACHE_FILENAME = "document_symbols.pkl"
|
||||
|
||||
# To be overridden and extended by subclasses
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
@@ -162,6 +218,7 @@ class SolidLanguageServer(ABC):
|
||||
process_launch_info: ProcessLaunchInfo,
|
||||
language_id: str,
|
||||
solidlsp_settings: SolidLSPSettings,
|
||||
cache_version_raw_document_symbols: int = 1,
|
||||
):
|
||||
"""
|
||||
Initializes a LanguageServer instance.
|
||||
@@ -175,6 +232,10 @@ class SolidLanguageServer(ABC):
|
||||
This parameter is the command to launch the language server process.
|
||||
The command must pass appropriate flags to the binary, so that it runs in the stdio mode,
|
||||
as opposed to HTTP, TCP modes supported by some language servers.
|
||||
:param cache_version_raw_document_symbols: the version, for caching, of the raw document symbols coming
|
||||
from this specific language server. This should be incremented by subclasses calling this constructor
|
||||
whenever the format of the raw document symbols changes (typically because the language server
|
||||
improves/fixes its output).
|
||||
"""
|
||||
self._solidlsp_settings = solidlsp_settings
|
||||
self._encoding = config.encoding
|
||||
@@ -189,14 +250,22 @@ class SolidLanguageServer(ABC):
|
||||
self.open_file_buffers: dict[str, LSPFileBuffer] = {}
|
||||
self.language = Language(language_id)
|
||||
|
||||
# load cache first to prevent any racing conditions due to asyncio stuff
|
||||
self._document_symbols_cache: dict[
|
||||
str, tuple[str, tuple[list[ls_types.UnifiedSymbolInformation], list[ls_types.UnifiedSymbolInformation]]]
|
||||
] = {}
|
||||
"""Maps file paths to a tuple of (file_content_hash, result_of_request_document_symbols)"""
|
||||
self._cache_lock = threading.Lock()
|
||||
self._cache_has_changed: bool = False
|
||||
self.load_cache()
|
||||
# initialise symbol caches
|
||||
self.cache_dir = (
|
||||
Path(self.repository_root_path) / self._solidlsp_settings.project_data_relative_path / self.CACHE_FOLDER_NAME / self.language_id
|
||||
)
|
||||
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
# * raw document symbols cache
|
||||
self._ls_specific_raw_document_symbols_cache_version = cache_version_raw_document_symbols
|
||||
self._raw_document_symbols_cache: dict[str, tuple[str, list[GenericDocumentSymbol]]] = {}
|
||||
"""maps relative file paths to a tuple of (file_content_hash, raw_root_symbols)"""
|
||||
self._raw_document_symbols_cache_is_modified: bool = False
|
||||
self._load_raw_document_symbols_cache()
|
||||
# * high-level document symbols cache
|
||||
self._document_symbols_cache: dict[str, tuple[str, DocumentSymbols]] = {}
|
||||
"""maps relative file paths to a tuple of (file_content_hash, document_symbols)"""
|
||||
self._document_symbols_cache_is_modified: bool = False
|
||||
self._load_document_symbols_cache()
|
||||
|
||||
self.server_started = False
|
||||
self.completions_available = threading.Event()
|
||||
@@ -422,6 +491,20 @@ class SolidLanguageServer(ABC):
|
||||
)
|
||||
del self.open_file_buffers[uri]
|
||||
|
||||
@contextmanager
|
||||
def _open_file_context(self, relative_file_path: str, file_buffer: LSPFileBuffer | None = None) -> 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.
|
||||
"""
|
||||
if file_buffer is not None:
|
||||
yield file_buffer
|
||||
else:
|
||||
with self.open_file(relative_file_path) 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:
|
||||
"""
|
||||
Insert text at the given line and column in the given file and return
|
||||
@@ -831,150 +914,174 @@ class SolidLanguageServer(ABC):
|
||||
|
||||
return [json.loads(json_repr) for json_repr in set(json.dumps(item, sort_keys=True) for item in completions_list)]
|
||||
|
||||
def _request_document_symbols(self, relative_file_path: str, include_body: bool = False) -> list[GenericDocumentSymbol]:
|
||||
def _request_document_symbols(self, relative_file_path: str, file_data: LSPFileBuffer | None) -> list[GenericDocumentSymbol]:
|
||||
"""
|
||||
Sends a [documentSymbol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentSymbol)
|
||||
request to the language server to find symbols in the given file, which is assumed to be opened while the call is active.
|
||||
Waits for the response and returns it.
|
||||
request to the language server to find symbols in the given file - or returns a cached result if available.
|
||||
|
||||
:param relative_file_path: the relative path of the file that has the symbols.
|
||||
IMPORTANT: It must be ensured that the file is already opened in the Language Server before calling this method.
|
||||
:param include_body: whether to include the body of the symbols in the result.
|
||||
:return: the list of symbols in the file.
|
||||
:param file_data: the file data buffer, if already opened. If None, the file will be opened in this method.
|
||||
:return: the list of root symbols in the file.
|
||||
"""
|
||||
self.logger.log(f"Requesting document symbols for {relative_file_path} from the Language Server", logging.DEBUG)
|
||||
response = self.server.send.document_symbol(
|
||||
{"textDocument": {"uri": pathlib.Path(os.path.join(self.repository_root_path, relative_file_path)).as_uri()}}
|
||||
)
|
||||
return response
|
||||
|
||||
def request_document_symbols(
|
||||
self, relative_file_path: str, include_body: bool = False
|
||||
) -> tuple[list[ls_types.UnifiedSymbolInformation], list[ls_types.UnifiedSymbolInformation]]:
|
||||
def get_cached_raw_document_symbols(cache_key: str, fd: LSPFileBuffer) -> list[GenericDocumentSymbol] | None:
|
||||
file_hash_and_result = self._raw_document_symbols_cache.get(cache_key)
|
||||
if file_hash_and_result is not None:
|
||||
file_hash, result = file_hash_and_result
|
||||
if file_hash == fd.content_hash:
|
||||
log.debug("Returning cached raw document symbols for %s", relative_file_path)
|
||||
return result
|
||||
else:
|
||||
log.debug("Document content for %s has changed (raw symbol cache is not up-to-date)", relative_file_path)
|
||||
else:
|
||||
log.debug("No cache hit for raw document symbols symbols in %s", relative_file_path)
|
||||
return None
|
||||
|
||||
def get_raw_document_symbols(fd: LSPFileBuffer) -> list[GenericDocumentSymbol]:
|
||||
# check for cached result
|
||||
cache_key = relative_file_path
|
||||
response = get_cached_raw_document_symbols(cache_key, fd)
|
||||
if response is not None:
|
||||
return response
|
||||
|
||||
# no cached result, query language server
|
||||
self.logger.log(f"Requesting document symbols for {relative_file_path} from the Language Server", logging.DEBUG)
|
||||
response = self.server.send.document_symbol(
|
||||
{"textDocument": {"uri": pathlib.Path(os.path.join(self.repository_root_path, relative_file_path)).as_uri()}}
|
||||
)
|
||||
|
||||
# update cache
|
||||
self._raw_document_symbols_cache[cache_key] = (file_data.content_hash, response)
|
||||
self._raw_document_symbols_cache_is_modified = True
|
||||
|
||||
return response
|
||||
|
||||
if file_data is not None:
|
||||
return get_raw_document_symbols(file_data)
|
||||
else:
|
||||
with self.open_file(relative_file_path) as opened_file_data:
|
||||
return get_raw_document_symbols(opened_file_data)
|
||||
|
||||
def request_document_symbols(self, relative_file_path: str, file_buffer: LSPFileBuffer | None = None) -> DocumentSymbols:
|
||||
"""
|
||||
Retrieves the full list of symbols in the given file, along with the root symbols that represent the tree structure of the symbols.
|
||||
Retrieves the collection of symbols in the given file
|
||||
|
||||
:param relative_file_path: The relative path of the file that has the symbols
|
||||
:param include_body: whether to include the body of the symbols in the result.
|
||||
:return: A list of symbols in the file, and a list of root symbols that represent the tree structure of the symbols.
|
||||
All symbols will have a location, children, and a parent attribute,
|
||||
:param file_buffer: an optional file buffer if the file is already opened.
|
||||
:return: the collection of symbols in the file.
|
||||
All contained symbols will have a location, children, and a parent attribute,
|
||||
where the parent attribute is None for root symbols.
|
||||
Note that this is slightly different from the call to request_full_symbol_tree,
|
||||
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.
|
||||
"""
|
||||
# check if the desired result is cached
|
||||
# TODO: it's kinda dumb to not use the cache if include_body is False after include_body was True once
|
||||
# Should be fixed in the future, it's a small performance optimization
|
||||
cache_key = f"{relative_file_path}-{include_body}"
|
||||
with self.open_file(relative_file_path) as file_data:
|
||||
with self._cache_lock:
|
||||
file_hash_and_result = self._document_symbols_cache.get(cache_key)
|
||||
if file_hash_and_result is not None:
|
||||
file_hash, result = file_hash_and_result
|
||||
if file_hash == file_data.content_hash:
|
||||
self.logger.log(f"Returning cached document symbols for {relative_file_path}", logging.DEBUG)
|
||||
return result
|
||||
else:
|
||||
self.logger.log(f"Content for {relative_file_path} has changed. Will overwrite in-memory cache", logging.DEBUG)
|
||||
with self._open_file_context(relative_file_path, file_buffer) 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)
|
||||
if file_hash_and_result is not None:
|
||||
file_hash, document_symbols = file_hash_and_result
|
||||
if file_hash == file_data.content_hash:
|
||||
log.debug("Returning cached document symbols for %s", relative_file_path)
|
||||
return document_symbols
|
||||
else:
|
||||
self.logger.log(f"No cache hit for symbols with {include_body=} in {relative_file_path}", logging.DEBUG)
|
||||
log.debug("Cached document symbol content for %s has changed", relative_file_path)
|
||||
else:
|
||||
log.debug("No cache hit for document symbols in %s", relative_file_path)
|
||||
|
||||
# request the symbols from the language server
|
||||
response = self._request_document_symbols(relative_file_path, include_body=include_body)
|
||||
# no cached result: request the root symbols from the language server
|
||||
root_symbols = self._request_document_symbols(relative_file_path, file_data)
|
||||
|
||||
if response is None:
|
||||
self.logger.log(
|
||||
f"Received None response from the Language Server for document symbols in {relative_file_path}. "
|
||||
f"This means the language server can't understand this file (possibly due to syntax errors). It may also be due to a bug or misconfiguration of the LS. "
|
||||
f"Returning empty list",
|
||||
logging.WARNING,
|
||||
)
|
||||
return [], []
|
||||
assert isinstance(response, list), f"Unexpected response from Language Server: {response}"
|
||||
self.logger.log(
|
||||
f"Received {len(response)} document symbols for {relative_file_path} from the Language Server",
|
||||
logging.DEBUG,
|
||||
)
|
||||
|
||||
def turn_item_into_symbol_with_children(item: GenericDocumentSymbol) -> ls_types.UnifiedSymbolInformation:
|
||||
"""
|
||||
Ensures that the given item is augmented with location, children, and body (if requested),
|
||||
such that it satisfies the UnifiedSymbolInformation structure.
|
||||
|
||||
:param item: the item to augment
|
||||
:return: the augmented item (same object as input, but with more attributes and cast to UnifiedSymbolInformation)
|
||||
"""
|
||||
item = cast(ls_types.UnifiedSymbolInformation, item)
|
||||
absolute_path = os.path.join(self.repository_root_path, relative_file_path)
|
||||
|
||||
# handle missing entries in location
|
||||
if "location" not in item:
|
||||
uri = pathlib.Path(absolute_path).as_uri()
|
||||
assert "range" in item
|
||||
tree_location = ls_types.Location(
|
||||
uri=uri,
|
||||
range=item["range"],
|
||||
absolutePath=absolute_path,
|
||||
relativePath=relative_file_path,
|
||||
if root_symbols is None:
|
||||
log.warning(
|
||||
f"Received None response from the Language Server for document symbols in {relative_file_path}. "
|
||||
f"This means the language server can't understand this file (possibly due to syntax errors). It may also be due to a bug or misconfiguration of the LS. "
|
||||
f"Returning empty list",
|
||||
)
|
||||
item["location"] = tree_location
|
||||
location = item["location"]
|
||||
if "absolutePath" not in location:
|
||||
location["absolutePath"] = absolute_path
|
||||
if "relativePath" not in location:
|
||||
location["relativePath"] = relative_file_path
|
||||
if include_body:
|
||||
item["body"] = self.retrieve_symbol_body(item)
|
||||
# handle missing selectionRange
|
||||
if "selectionRange" not in item:
|
||||
if "range" in item:
|
||||
item["selectionRange"] = item["range"]
|
||||
else:
|
||||
item["selectionRange"] = item["location"]["range"]
|
||||
children = item.get(LSPConstants.CHILDREN, [])
|
||||
for child in children:
|
||||
child["parent"] = item
|
||||
item[LSPConstants.CHILDREN] = children
|
||||
return item
|
||||
return DocumentSymbols([])
|
||||
|
||||
flat_all_symbol_list: list[ls_types.UnifiedSymbolInformation] = []
|
||||
assert isinstance(root_symbols, list), f"Unexpected response from Language Server: {root_symbols}"
|
||||
log.debug("Received %d root symbols for %s from the language server", len(root_symbols), relative_file_path)
|
||||
|
||||
def convert_nodes_with_common_parent(nodes: list[GenericDocumentSymbol], parent: ls_types.UnifiedSymbolInformation | None) -> None:
|
||||
"""
|
||||
Converts the given nodes into UnifiedSymbolInformation with proper parent-child relationships,
|
||||
adding overload indices for symbols with the same name under the same parent.
|
||||
"""
|
||||
total_name_counts = defaultdict(lambda: 0)
|
||||
for node in nodes:
|
||||
total_name_counts[node["name"]] += 1
|
||||
name_counts = defaultdict(lambda: 0)
|
||||
for node in nodes:
|
||||
flat_all_symbol_list.append(node)
|
||||
usymbol_node = turn_item_into_symbol_with_children(node)
|
||||
if total_name_counts[usymbol_node["name"]] > 1:
|
||||
usymbol_node["overload_idx"] = name_counts[usymbol_node["name"]]
|
||||
name_counts[usymbol_node["name"]] += 1
|
||||
usymbol_node["parent"] = parent
|
||||
if usymbol_node["children"]:
|
||||
convert_nodes_with_common_parent(usymbol_node["children"], usymbol_node)
|
||||
file_lines = file_data.split_lines()
|
||||
|
||||
root_nodes: list[ls_types.UnifiedSymbolInformation] = response
|
||||
convert_nodes_with_common_parent(root_nodes, None)
|
||||
def convert_to_unified_symbol(original_symbol_dict: GenericDocumentSymbol) -> ls_types.UnifiedSymbolInformation:
|
||||
"""
|
||||
Converts the given symbol dictionary to the unified representation, ensuring
|
||||
that all required fields are present (except 'children' which is handled separately).
|
||||
|
||||
result = flat_all_symbol_list, root_nodes
|
||||
:param original_symbol_dict: the item to augment
|
||||
:return: the augmented item (new object)
|
||||
"""
|
||||
# noinspection PyInvalidCast
|
||||
item = cast(ls_types.UnifiedSymbolInformation, dict(original_symbol_dict))
|
||||
absolute_path = os.path.join(self.repository_root_path, relative_file_path)
|
||||
|
||||
# update cache
|
||||
self.logger.log(f"Caching document symbols for {relative_file_path}", logging.DEBUG)
|
||||
with self._cache_lock:
|
||||
self._document_symbols_cache[cache_key] = (file_data.content_hash, result)
|
||||
self._cache_has_changed = True
|
||||
# handle missing location and path entries
|
||||
if "location" not in item:
|
||||
uri = pathlib.Path(absolute_path).as_uri()
|
||||
assert "range" in item
|
||||
tree_location = ls_types.Location(
|
||||
uri=uri,
|
||||
range=item["range"],
|
||||
absolutePath=absolute_path,
|
||||
relativePath=relative_file_path,
|
||||
)
|
||||
item["location"] = tree_location
|
||||
location = item["location"]
|
||||
if "absolutePath" not in location:
|
||||
location["absolutePath"] = absolute_path
|
||||
if "relativePath" not in location:
|
||||
location["relativePath"] = relative_file_path
|
||||
|
||||
return result
|
||||
if "body" not in item:
|
||||
item["body"] = self.retrieve_symbol_body(item, file_lines=file_lines)
|
||||
|
||||
def request_full_symbol_tree(
|
||||
self, within_relative_path: str | None = None, include_body: bool = False
|
||||
) -> list[ls_types.UnifiedSymbolInformation]:
|
||||
# handle missing selectionRange
|
||||
if "selectionRange" not in item:
|
||||
if "range" in item:
|
||||
item["selectionRange"] = item["range"]
|
||||
else:
|
||||
item["selectionRange"] = item["location"]["range"]
|
||||
|
||||
return item
|
||||
|
||||
def convert_symbols_with_common_parent(
|
||||
symbols: list[GenericDocumentSymbol], parent: ls_types.UnifiedSymbolInformation | None
|
||||
) -> list[ls_types.UnifiedSymbolInformation]:
|
||||
"""
|
||||
Converts the given symbols into UnifiedSymbolInformation with proper parent-child relationships,
|
||||
adding overload indices for symbols with the same name under the same parent.
|
||||
"""
|
||||
total_name_counts = defaultdict(lambda: 0)
|
||||
for symbol in symbols:
|
||||
total_name_counts[symbol["name"]] += 1
|
||||
name_counts = defaultdict(lambda: 0)
|
||||
unified_symbols = []
|
||||
for symbol in symbols:
|
||||
usymbol = convert_to_unified_symbol(symbol)
|
||||
if total_name_counts[usymbol["name"]] > 1:
|
||||
usymbol["overload_idx"] = name_counts[usymbol["name"]]
|
||||
name_counts[usymbol["name"]] += 1
|
||||
usymbol["parent"] = parent
|
||||
if "children" in usymbol:
|
||||
usymbol["children"] = convert_symbols_with_common_parent(usymbol["children"], usymbol)
|
||||
else:
|
||||
usymbol["children"] = []
|
||||
unified_symbols.append(usymbol)
|
||||
return unified_symbols
|
||||
|
||||
unified_root_symbols = convert_symbols_with_common_parent(root_symbols, None)
|
||||
document_symbols = DocumentSymbols(unified_root_symbols)
|
||||
|
||||
# update cache
|
||||
log.debug("Updating cached document symbols for %s", relative_file_path)
|
||||
self._document_symbols_cache[cache_key] = (file_data.content_hash, document_symbols)
|
||||
self._document_symbols_cache_is_modified = True
|
||||
|
||||
return document_symbols
|
||||
|
||||
def request_full_symbol_tree(self, within_relative_path: str | None = None) -> list[ls_types.UnifiedSymbolInformation]:
|
||||
"""
|
||||
Will go through all files in the project or within a relative path and build a tree of symbols.
|
||||
Note: this may be slow the first time it is called, especially if `within_relative_path` is not used to restrict the search.
|
||||
@@ -989,8 +1096,6 @@ class SolidLanguageServer(ABC):
|
||||
:param within_relative_path: pass a relative path to only consider symbols within this path.
|
||||
If a file is passed, only the symbols within this file will be considered.
|
||||
If a directory is passed, all files within this directory will be considered.
|
||||
:param include_body: whether to include the body of the symbols in the result.
|
||||
|
||||
:return: A list of root symbols representing the top-level packages/modules in the project.
|
||||
"""
|
||||
if within_relative_path is not None:
|
||||
@@ -999,13 +1104,10 @@ class SolidLanguageServer(ABC):
|
||||
raise FileNotFoundError(f"File or directory not found: {within_abs_path}")
|
||||
if os.path.isfile(within_abs_path):
|
||||
if self.is_ignored_path(within_relative_path):
|
||||
self.logger.log(
|
||||
f"You passed a file explicitly, but it is ignored. This is probably an error. File: {within_relative_path}",
|
||||
logging.ERROR,
|
||||
)
|
||||
log.error("You passed a file explicitly, but it is ignored. This is probably an error. File: %s", within_relative_path)
|
||||
return []
|
||||
else:
|
||||
_, root_nodes = self.request_document_symbols(within_relative_path, include_body=include_body)
|
||||
root_nodes = self.request_document_symbols(within_relative_path).root_symbols
|
||||
return root_nodes
|
||||
|
||||
# Helper function to recursively process directories
|
||||
@@ -1014,7 +1116,7 @@ class SolidLanguageServer(ABC):
|
||||
abs_dir_path = os.path.realpath(abs_dir_path)
|
||||
|
||||
if self.is_ignored_path(str(Path(abs_dir_path).relative_to(self.repository_root_path))):
|
||||
self.logger.log(f"Skipping directory: {rel_dir_path}\n(because it should be ignored)", logging.DEBUG)
|
||||
log.debug("Skipping directory: %s (because it should be ignored)", rel_dir_path)
|
||||
return []
|
||||
|
||||
result = []
|
||||
@@ -1047,14 +1149,16 @@ class SolidLanguageServer(ABC):
|
||||
)
|
||||
except ValueError as e:
|
||||
# Typically happens when the path is not under the repository root (e.g., symlink pointing outside)
|
||||
self.logger.log(
|
||||
f"Skipping path {contained_dir_or_file_abs_path}; likely outside of the repository root {self.repository_root_path} [cause: {e}]",
|
||||
logging.WARNING,
|
||||
log.warning(
|
||||
"Skipping path %s; likely outside of the repository root %s [cause: %s]",
|
||||
contained_dir_or_file_abs_path,
|
||||
self.repository_root_path,
|
||||
e,
|
||||
)
|
||||
continue
|
||||
|
||||
if self.is_ignored_path(contained_dir_or_file_rel_path):
|
||||
self.logger.log(f"Skipping item: {contained_dir_or_file_rel_path}\n(because it should be ignored)", logging.DEBUG)
|
||||
log.debug("Skipping item: %s (because it should be ignored)", contained_dir_or_file_rel_path)
|
||||
continue
|
||||
|
||||
if os.path.isdir(contained_dir_or_file_abs_path):
|
||||
@@ -1064,28 +1168,28 @@ class SolidLanguageServer(ABC):
|
||||
child["parent"] = package_symbol
|
||||
|
||||
elif os.path.isfile(contained_dir_or_file_abs_path):
|
||||
_, file_root_nodes = self.request_document_symbols(contained_dir_or_file_rel_path, include_body=include_body)
|
||||
with self._open_file_context(contained_dir_or_file_rel_path) as file_data:
|
||||
document_symbols = self.request_document_symbols(contained_dir_or_file_rel_path, file_data)
|
||||
file_root_nodes = document_symbols.root_symbols
|
||||
|
||||
# Create file symbol, link with children
|
||||
file_rel_path = str(Path(contained_dir_or_file_abs_path).resolve().relative_to(self.repository_root_path))
|
||||
with self.open_file(file_rel_path) as file_data:
|
||||
fileRange = self._get_range_from_file_content(file_data.contents)
|
||||
file_symbol = ls_types.UnifiedSymbolInformation( # type: ignore
|
||||
name=os.path.splitext(contained_dir_or_file_name)[0],
|
||||
kind=ls_types.SymbolKind.File,
|
||||
range=fileRange,
|
||||
selectionRange=fileRange,
|
||||
location=ls_types.Location(
|
||||
uri=str(pathlib.Path(contained_dir_or_file_abs_path).as_uri()),
|
||||
range=fileRange,
|
||||
absolutePath=str(contained_dir_or_file_abs_path),
|
||||
relativePath=str(Path(contained_dir_or_file_abs_path).resolve().relative_to(self.repository_root_path)),
|
||||
),
|
||||
children=file_root_nodes,
|
||||
parent=package_symbol,
|
||||
)
|
||||
for child in file_root_nodes:
|
||||
child["parent"] = file_symbol
|
||||
# Create file symbol, link with children
|
||||
file_range = self._get_range_from_file_content(file_data.contents)
|
||||
file_symbol = ls_types.UnifiedSymbolInformation( # type: ignore
|
||||
name=os.path.splitext(contained_dir_or_file_name)[0],
|
||||
kind=ls_types.SymbolKind.File,
|
||||
range=file_range,
|
||||
selectionRange=file_range,
|
||||
location=ls_types.Location(
|
||||
uri=str(pathlib.Path(contained_dir_or_file_abs_path).as_uri()),
|
||||
range=file_range,
|
||||
absolutePath=str(contained_dir_or_file_abs_path),
|
||||
relativePath=str(Path(contained_dir_or_file_abs_path).resolve().relative_to(self.repository_root_path)),
|
||||
),
|
||||
children=file_root_nodes,
|
||||
parent=package_symbol,
|
||||
)
|
||||
for child in file_root_nodes:
|
||||
child["parent"] = file_symbol
|
||||
|
||||
# Link file symbol with package
|
||||
package_symbol["children"].append(file_symbol)
|
||||
@@ -1173,8 +1277,7 @@ class SolidLanguageServer(ABC):
|
||||
"""
|
||||
:return: the top-level symbols in the given file.
|
||||
"""
|
||||
_, document_roots = self.request_document_symbols(relative_file_path)
|
||||
return document_roots
|
||||
return self.request_document_symbols(relative_file_path).root_symbols
|
||||
|
||||
def request_overview(self, within_relative_path: str) -> dict[str, list[UnifiedSymbolInformation]]:
|
||||
"""
|
||||
@@ -1222,7 +1325,12 @@ class SolidLanguageServer(ABC):
|
||||
|
||||
return ls_types.Hover(**response)
|
||||
|
||||
def retrieve_symbol_body(self, symbol: ls_types.UnifiedSymbolInformation | LSPTypes.DocumentSymbol | LSPTypes.SymbolInformation) -> str:
|
||||
def retrieve_symbol_body(
|
||||
self,
|
||||
symbol: ls_types.UnifiedSymbolInformation | LSPTypes.DocumentSymbol | LSPTypes.SymbolInformation,
|
||||
file_lines: list[str] | None = None,
|
||||
file_buffer: LSPFileBuffer | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
Load the body of the given symbol. If the body is already contained in the symbol, just return it.
|
||||
"""
|
||||
@@ -1234,9 +1342,10 @@ class SolidLanguageServer(ABC):
|
||||
symbol_start_line = symbol["location"]["range"]["start"]["line"]
|
||||
symbol_end_line = symbol["location"]["range"]["end"]["line"]
|
||||
assert "relativePath" in symbol["location"]
|
||||
symbol_file = self.retrieve_full_file_content(symbol["location"]["relativePath"])
|
||||
symbol_lines = symbol_file.split("\n")
|
||||
symbol_body = "\n".join(symbol_lines[symbol_start_line : symbol_end_line + 1])
|
||||
if file_lines is None:
|
||||
with self._open_file_context(symbol["location"]["relativePath"], file_buffer) as f:
|
||||
file_lines = f.split_lines()
|
||||
symbol_body = "\n".join(file_lines[symbol_start_line : symbol_end_line + 1])
|
||||
|
||||
# remove leading indentation
|
||||
symbol_start_column = symbol["location"]["range"]["start"]["character"]
|
||||
@@ -1311,8 +1420,8 @@ class SolidLanguageServer(ABC):
|
||||
ref_text = file_data.contents.split("\n")[ref_line]
|
||||
if "." in ref_text:
|
||||
containing_symbol_name = ref_text.split(".")[0]
|
||||
all_symbols, _ = self.request_document_symbols(ref_path)
|
||||
for symbol in all_symbols:
|
||||
document_symbols = self.request_document_symbols(ref_path)
|
||||
for symbol in document_symbols.iter_symbols():
|
||||
if symbol["name"] == containing_symbol_name and symbol["kind"] == ls_types.SymbolKind.Variable:
|
||||
containing_symbol = copy(symbol)
|
||||
containing_symbol["location"] = ref
|
||||
@@ -1433,12 +1542,12 @@ class SolidLanguageServer(ABC):
|
||||
)
|
||||
return None
|
||||
|
||||
symbols, _ = self.request_document_symbols(relative_file_path)
|
||||
document_symbols = self.request_document_symbols(relative_file_path)
|
||||
|
||||
# make jedi and pyright api compatible
|
||||
# the former has no location, the later has no range
|
||||
# we will just always add location of the desired format to all symbols
|
||||
for symbol in symbols:
|
||||
for symbol in document_symbols.iter_symbols():
|
||||
if "location" not in symbol:
|
||||
range = symbol["range"]
|
||||
location = ls_types.Location(
|
||||
@@ -1476,10 +1585,10 @@ class SolidLanguageServer(ABC):
|
||||
# Only consider containers that are not one-liners (otherwise we may get imports)
|
||||
candidate_containers = [
|
||||
s
|
||||
for s in symbols
|
||||
for s in document_symbols.iter_symbols()
|
||||
if s["kind"] in container_symbol_kinds and s["location"]["range"]["start"]["line"] != s["location"]["range"]["end"]["line"]
|
||||
]
|
||||
var_containers = [s for s in symbols if s["kind"] == ls_types.SymbolKind.Variable]
|
||||
var_containers = [s for s in document_symbols.iter_symbols() if s["kind"] == ls_types.SymbolKind.Variable]
|
||||
candidate_containers.extend(var_containers)
|
||||
|
||||
if not candidate_containers:
|
||||
@@ -1566,56 +1675,110 @@ class SolidLanguageServer(ABC):
|
||||
|
||||
return defining_symbol
|
||||
|
||||
@property
|
||||
def cache_path(self) -> Path:
|
||||
"""
|
||||
The path to the cache file for the document symbols.
|
||||
"""
|
||||
return (
|
||||
Path(self.repository_root_path)
|
||||
/ self._solidlsp_settings.project_data_relative_path
|
||||
/ self.CACHE_FOLDER_NAME
|
||||
/ self.language_id
|
||||
/ "document_symbols_cache.pkl"
|
||||
)
|
||||
def _save_raw_document_symbols_cache(self):
|
||||
cache_file = self.cache_dir / self.RAW_DOCUMENT_SYMBOL_CACHE_FILENAME
|
||||
|
||||
def save_cache(self):
|
||||
with self._cache_lock:
|
||||
if not self._cache_has_changed:
|
||||
self.logger.log("No changes to document symbols cache, skipping save", logging.DEBUG)
|
||||
return
|
||||
|
||||
self.logger.log(f"Saving updated document symbols cache to {self.cache_path}", logging.INFO)
|
||||
self.cache_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
save_cache(str(self.cache_path), self.CACHE_VERSION, self._document_symbols_cache)
|
||||
self._cache_has_changed = False
|
||||
except Exception as e:
|
||||
self.logger.log(
|
||||
f"Failed to save document symbols cache to {self.cache_path}: {e}. "
|
||||
"Note: this may have resulted in a corrupted cache file.",
|
||||
logging.ERROR,
|
||||
)
|
||||
|
||||
def load_cache(self):
|
||||
if not self.cache_path.exists():
|
||||
if not self._raw_document_symbols_cache_is_modified:
|
||||
log.debug("No changes to raw document symbols cache, skipping save")
|
||||
return
|
||||
|
||||
with self._cache_lock:
|
||||
self.logger.log(f"Loading document symbols cache from {self.cache_path}", logging.INFO)
|
||||
log.info("Saving updated raw document symbols cache to %s", cache_file)
|
||||
try:
|
||||
save_cache(str(cache_file), self._raw_document_symbols_cache_version(), self._raw_document_symbols_cache)
|
||||
self._raw_document_symbols_cache_is_modified = False
|
||||
except Exception as e:
|
||||
log.error(
|
||||
"Failed to save raw document symbols cache to %s: %s. Note: this may have resulted in a corrupted cache file.",
|
||||
cache_file,
|
||||
e,
|
||||
)
|
||||
|
||||
def _raw_document_symbols_cache_version(self):
|
||||
return (self.RAW_DOCUMENT_SYMBOLS_CACHE_VERSION, self._ls_specific_raw_document_symbols_cache_version)
|
||||
|
||||
def _load_raw_document_symbols_cache(self):
|
||||
cache_file = self.cache_dir / self.RAW_DOCUMENT_SYMBOL_CACHE_FILENAME
|
||||
|
||||
if not cache_file.exists():
|
||||
# check for legacy cache to load to migrate
|
||||
legacy_cache_file = self.cache_dir / self.RAW_DOCUMENT_SYMBOL_CACHE_FILENAME_LEGACY_FALLBACK
|
||||
if legacy_cache_file.exists():
|
||||
try:
|
||||
legacy_cache: dict[
|
||||
str, tuple[str, tuple[list[ls_types.UnifiedSymbolInformation], list[ls_types.UnifiedSymbolInformation]]]
|
||||
] = load_pickle(legacy_cache_file)
|
||||
log.info("Migrating legacy document symbols cache with %d entries", len(legacy_cache))
|
||||
num_symbols_migrated = 0
|
||||
migrated_cache = {}
|
||||
for cache_key, (file_hash, (all_symbols, root_symbols)) in legacy_cache.items():
|
||||
if cache_key.endswith("-True"): # include_body=True
|
||||
new_cache_key = cache_key[:-5]
|
||||
migrated_cache[new_cache_key] = (file_hash, root_symbols)
|
||||
num_symbols_migrated += len(all_symbols)
|
||||
log.info("Migrated %d document symbols from legacy cache", num_symbols_migrated)
|
||||
self._raw_document_symbols_cache = migrated_cache
|
||||
return
|
||||
except Exception as e:
|
||||
log.error("Failed to migrate cache: %s", e)
|
||||
return
|
||||
|
||||
# load existing cache (if any)
|
||||
if cache_file.exists():
|
||||
log.info("Loading document symbols cache from %s", cache_file)
|
||||
try:
|
||||
saved_cache = load_cache(str(self.cache_path), self.CACHE_VERSION)
|
||||
saved_cache = load_cache(str(cache_file), self._raw_document_symbols_cache_version())
|
||||
if saved_cache is not None:
|
||||
self._raw_document_symbols_cache = saved_cache
|
||||
self.logger.log(
|
||||
f"Loaded {len(self._raw_document_symbols_cache)} entries from raw document symbols cache.", logging.INFO
|
||||
)
|
||||
except Exception as e:
|
||||
# cache can become corrupt, so just skip loading it
|
||||
log.warning(
|
||||
"Failed to load raw document symbols cache from %s (%s); Ignoring cache.",
|
||||
cache_file,
|
||||
e,
|
||||
)
|
||||
|
||||
def _save_document_symbols_cache(self):
|
||||
cache_file = self.cache_dir / self.DOCUMENT_SYMBOL_CACHE_FILENAME
|
||||
|
||||
if not self._document_symbols_cache_is_modified:
|
||||
log.debug("No changes to document symbols cache, skipping save")
|
||||
return
|
||||
|
||||
log.info("Saving updated document symbols cache to %s", cache_file)
|
||||
try:
|
||||
save_cache(str(cache_file), self.DOCUMENT_SYMBOL_CACHE_VERSION, self._document_symbols_cache)
|
||||
self._document_symbols_cache_is_modified = False
|
||||
except Exception as e:
|
||||
log.error(
|
||||
"Failed to save document symbols cache to %s: %s. Note: this may have resulted in a corrupted cache file.",
|
||||
cache_file,
|
||||
e,
|
||||
)
|
||||
|
||||
def _load_document_symbols_cache(self):
|
||||
cache_file = self.cache_dir / self.DOCUMENT_SYMBOL_CACHE_FILENAME
|
||||
if cache_file.exists():
|
||||
log.info("Loading document symbols cache from %s", cache_file)
|
||||
try:
|
||||
saved_cache = load_cache(str(cache_file), self.DOCUMENT_SYMBOL_CACHE_VERSION)
|
||||
if saved_cache is not None:
|
||||
self._document_symbols_cache = saved_cache
|
||||
self.logger.log(f"Loaded {len(self._document_symbols_cache)} document symbols from cache.", logging.INFO)
|
||||
self.logger.log(f"Loaded {len(self._document_symbols_cache)} entries from document symbols cache.", logging.INFO)
|
||||
except Exception as e:
|
||||
# cache often becomes corrupt, so just skip loading it
|
||||
self.logger.log(
|
||||
f"Failed to load document symbols cache from {self.cache_path}: {e}. Possible cause: the cache file is corrupted. "
|
||||
"Check for any errors related to saving the cache in the logs.",
|
||||
logging.ERROR,
|
||||
# cache can become corrupt, so just skip loading it
|
||||
log.warning(
|
||||
"Failed to load document symbols cache from %s (%s); Ignoring cache.",
|
||||
cache_file,
|
||||
e,
|
||||
)
|
||||
|
||||
def save_cache(self):
|
||||
self._save_raw_document_symbols_cache()
|
||||
self._save_document_symbols_cache()
|
||||
|
||||
def request_workspace_symbol(self, query: str) -> list[ls_types.UnifiedSymbolInformation] | None:
|
||||
"""
|
||||
Raise a [workspace/symbol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_symbol) request to the Language Server
|
||||
|
||||
@@ -43,7 +43,7 @@ class TestALLanguageServer:
|
||||
def test_find_table_fields(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that AL Language Server can find fields within a table."""
|
||||
file_path = os.path.join("src", "Tables", "Customer.Table.al")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
# AL tables should have their fields as child symbols
|
||||
customer_table = None
|
||||
@@ -76,7 +76,7 @@ class TestALLanguageServer:
|
||||
def test_find_procedures(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that AL Language Server can find procedures in codeunits."""
|
||||
file_path = os.path.join("src", "Codeunits", "CustomerMgt.Codeunit.al")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
# Find the codeunit symbol - AL returns 'Codeunit 50000 CustomerMgt'
|
||||
codeunit_symbol = None
|
||||
@@ -100,7 +100,7 @@ class TestALLanguageServer:
|
||||
"""Test that AL Language Server can find references to symbols."""
|
||||
# Find references to the Customer table from the CustomerMgt codeunit
|
||||
table_file = os.path.join("src", "Tables", "Customer.Table.al")
|
||||
symbols = language_server.request_document_symbols(table_file)
|
||||
symbols = language_server.request_document_symbols(table_file).get_all_symbols_and_roots()
|
||||
|
||||
# Find the Customer table symbol
|
||||
customer_symbol = None
|
||||
|
||||
@@ -25,7 +25,7 @@ class TestBashLanguageServerBasics:
|
||||
def test_bash_request_document_symbols(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_document_symbols for bash files."""
|
||||
# Test getting symbols from main.sh
|
||||
all_symbols, _root_symbols = language_server.request_document_symbols("main.sh", include_body=False)
|
||||
all_symbols, _root_symbols = language_server.request_document_symbols("main.sh").get_all_symbols_and_roots()
|
||||
|
||||
# Extract function symbols (LSP Symbol Kind 12)
|
||||
function_symbols = [symbol for symbol in all_symbols if symbol.get("kind") == 12]
|
||||
@@ -41,7 +41,7 @@ class TestBashLanguageServerBasics:
|
||||
def test_bash_request_document_symbols_with_body(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_document_symbols with body extraction."""
|
||||
# Test with include_body=True
|
||||
all_symbols, _root_symbols = language_server.request_document_symbols("main.sh", include_body=True)
|
||||
all_symbols, _root_symbols = language_server.request_document_symbols("main.sh").get_all_symbols_and_roots()
|
||||
|
||||
function_symbols = [symbol for symbol in all_symbols if symbol.get("kind") == 12]
|
||||
|
||||
@@ -58,7 +58,7 @@ class TestBashLanguageServerBasics:
|
||||
def test_bash_utils_functions(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test function detection in utils.sh file."""
|
||||
# Test with utils.sh as well
|
||||
utils_all_symbols, _utils_root_symbols = language_server.request_document_symbols("utils.sh", include_body=False)
|
||||
utils_all_symbols, _utils_root_symbols = language_server.request_document_symbols("utils.sh").get_all_symbols_and_roots()
|
||||
|
||||
utils_function_symbols = [symbol for symbol in utils_all_symbols if symbol.get("kind") == 12]
|
||||
utils_function_names = [symbol["name"] for symbol in utils_function_symbols]
|
||||
@@ -84,12 +84,12 @@ class TestBashLanguageServerBasics:
|
||||
def test_bash_function_syntax_patterns(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that LSP detects different bash function syntax patterns correctly."""
|
||||
# Test main.sh (has both 'function' keyword and traditional syntax)
|
||||
main_all_symbols, _main_root_symbols = language_server.request_document_symbols("main.sh", include_body=False)
|
||||
main_all_symbols, _main_root_symbols = language_server.request_document_symbols("main.sh").get_all_symbols_and_roots()
|
||||
main_functions = [symbol for symbol in main_all_symbols if symbol.get("kind") == 12]
|
||||
main_function_names = [func["name"] for func in main_functions]
|
||||
|
||||
# Test utils.sh (all use 'function' keyword)
|
||||
utils_all_symbols, _utils_root_symbols = language_server.request_document_symbols("utils.sh", include_body=False)
|
||||
utils_all_symbols, _utils_root_symbols = language_server.request_document_symbols("utils.sh").get_all_symbols_and_roots()
|
||||
utils_functions = [symbol for symbol in utils_all_symbols if symbol.get("kind") == 12]
|
||||
utils_function_names = [func["name"] for func in utils_functions]
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ class TestLanguageServerBasics:
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
|
||||
def test_document_symbols(self, language_server: SolidLanguageServer):
|
||||
symbols, _ = language_server.request_document_symbols(CORE_PATH)
|
||||
symbols, _ = language_server.request_document_symbols(CORE_PATH).get_all_symbols_and_roots()
|
||||
|
||||
assert isinstance(symbols, list) and len(symbols) >= 4, "greet, add, multiply, -main functions"
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class TestCSharpLanguageServer:
|
||||
def test_get_document_symbols(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test getting document symbols from a C# file."""
|
||||
file_path = os.path.join("Program.cs")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
# Check that we have symbols
|
||||
assert len(symbols) > 0
|
||||
@@ -49,7 +49,7 @@ class TestCSharpLanguageServer:
|
||||
def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding references using symbol selection range."""
|
||||
file_path = os.path.join("Program.cs")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
add_symbol = None
|
||||
# Handle nested symbol structure
|
||||
symbol_list = symbols[0] if symbols and isinstance(symbols[0], list) else symbols
|
||||
@@ -68,7 +68,7 @@ class TestCSharpLanguageServer:
|
||||
def test_nested_namespace_symbols(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test getting symbols from nested namespace."""
|
||||
file_path = os.path.join("Models", "Person.cs")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
# Check that we have symbols
|
||||
assert len(symbols) > 0
|
||||
@@ -93,7 +93,7 @@ class TestCSharpLanguageServer:
|
||||
"""Test finding references to Calculator.Subtract method across files."""
|
||||
# First, find the Subtract method in Program.cs
|
||||
file_path = os.path.join("Program.cs")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
# Flatten the symbols if they're nested
|
||||
symbol_list = symbols[0] if symbols and isinstance(symbols[0], list) else symbols
|
||||
|
||||
@@ -171,7 +171,7 @@ class TestDartLanguageServer:
|
||||
def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding references using symbol selection range."""
|
||||
file_path = os.path.join("lib", "main.dart")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
# Handle nested symbol structure - symbols can be nested in lists
|
||||
symbol_list = symbols[0] if symbols and isinstance(symbols[0], list) else symbols
|
||||
@@ -281,7 +281,7 @@ class TestDartLanguageServer:
|
||||
def test_request_document_symbols(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test getting document symbols from a Dart file."""
|
||||
file_path = os.path.join("lib", "main.dart")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
# Check that we have symbols
|
||||
assert len(symbols) > 0
|
||||
@@ -308,7 +308,7 @@ class TestDartLanguageServer:
|
||||
def test_request_referencing_symbols_comprehensive(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test comprehensive referencing symbols functionality."""
|
||||
file_path = os.path.join("lib", "main.dart")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
# Handle nested symbol structure
|
||||
symbol_list = symbols[0] if symbols and isinstance(symbols[0], list) else symbols
|
||||
@@ -339,7 +339,7 @@ class TestDartLanguageServer:
|
||||
helper_file_path = os.path.join("lib", "helper.dart")
|
||||
|
||||
# Test finding references to subtract function from helper.dart in main.dart
|
||||
helper_symbols = language_server.request_document_symbols(helper_file_path)
|
||||
helper_symbols = language_server.request_document_symbols(helper_file_path).get_all_symbols_and_roots()
|
||||
symbol_list = helper_symbols[0] if helper_symbols and isinstance(helper_symbols[0], list) else helper_symbols
|
||||
|
||||
subtract_symbol = next((s for s in symbol_list if s.get("name") == "subtract"), None)
|
||||
|
||||
@@ -25,7 +25,7 @@ class TestElixirBasic:
|
||||
def test_request_references_function_definition(self, language_server: SolidLanguageServer):
|
||||
"""Test finding references to a function definition."""
|
||||
file_path = os.path.join("lib", "models.ex")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
# Find the User module's 'new' function
|
||||
user_new_symbol = None
|
||||
@@ -54,7 +54,7 @@ class TestElixirBasic:
|
||||
def test_request_references_create_user_function(self, language_server: SolidLanguageServer):
|
||||
"""Test finding references to create_user function."""
|
||||
file_path = os.path.join("lib", "services.ex")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
# Find the UserService module's 'create_user' function
|
||||
create_user_symbol = None
|
||||
@@ -79,7 +79,7 @@ class TestElixirBasic:
|
||||
def test_request_referencing_symbols_function(self, language_server: SolidLanguageServer):
|
||||
"""Test finding symbols that reference a specific function."""
|
||||
file_path = os.path.join("lib", "models.ex")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
# Find the User module's 'new' function
|
||||
user_new_symbol = None
|
||||
@@ -103,10 +103,10 @@ class TestElixirBasic:
|
||||
def test_timeout_enumeration_bug(self, language_server: SolidLanguageServer):
|
||||
"""Test that enumeration doesn't timeout (regression test)."""
|
||||
# This should complete without timing out
|
||||
symbols = language_server.request_document_symbols("lib/models.ex")
|
||||
symbols = language_server.request_document_symbols("lib/models.ex").get_all_symbols_and_roots()
|
||||
assert symbols is not None
|
||||
|
||||
# Test multiple symbol requests in succession
|
||||
for _ in range(3):
|
||||
symbols = language_server.request_document_symbols("lib/services.ex")
|
||||
symbols = language_server.request_document_symbols("lib/services.ex").get_all_symbols_and_roots()
|
||||
assert symbols is not None
|
||||
@@ -46,7 +46,7 @@ def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
|
||||
definition_file = "lib/models.ex"
|
||||
|
||||
# Find the User struct definition
|
||||
symbols = ls_with_ignored_dirs.request_document_symbols(definition_file)
|
||||
symbols = ls_with_ignored_dirs.request_document_symbols(definition_file).get_all_symbols_and_roots()
|
||||
user_symbol = None
|
||||
for symbol_group in symbols:
|
||||
user_symbol = next((s for s in symbol_group if "User" in s.get("name", "")), None)
|
||||
@@ -87,7 +87,7 @@ def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None:
|
||||
definition_file = "lib/models.ex"
|
||||
|
||||
# Find the User struct definition
|
||||
symbols = ls.request_document_symbols(definition_file)
|
||||
symbols = ls.request_document_symbols(definition_file).get_all_symbols_and_roots()
|
||||
user_symbol = None
|
||||
for symbol_group in symbols:
|
||||
user_symbol = next((s for s in symbol_group if "User" in s.get("name", "")), None)
|
||||
|
||||
@@ -72,7 +72,7 @@ class TestElixirIntegration:
|
||||
def test_module_hierarchy_understanding(self, language_server: SolidLanguageServer):
|
||||
"""Test that the language server understands Elixir module hierarchy."""
|
||||
models_file = os.path.join("lib", "models.ex")
|
||||
symbols = language_server.request_document_symbols(models_file)
|
||||
symbols = language_server.request_document_symbols(models_file).get_all_symbols_and_roots()
|
||||
|
||||
if symbols:
|
||||
# Flatten symbol structure
|
||||
|
||||
@@ -120,7 +120,7 @@ class TestElixirLanguageServerSymbols:
|
||||
# Test referencing symbols for User struct
|
||||
file_path = os.path.join("lib", "models.ex")
|
||||
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
user_symbol = None
|
||||
for symbol_group in symbols:
|
||||
user_symbol = next((s for s in symbol_group if "User" in s.get("name", "")), None)
|
||||
|
||||
@@ -20,7 +20,7 @@ class TestElmLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.ELM], indirect=True)
|
||||
def test_find_references_within_file(self, language_server: SolidLanguageServer) -> None:
|
||||
file_path = os.path.join("Main.elm")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
greet_symbol = None
|
||||
for sym in symbols[0]:
|
||||
if sym.get("name") == "greet":
|
||||
@@ -35,7 +35,7 @@ class TestElmLanguageServer:
|
||||
def test_find_references_across_files(self, language_server: SolidLanguageServer) -> None:
|
||||
# Test formatMessage function which is defined in Utils.elm and used in Main.elm
|
||||
utils_path = os.path.join("Utils.elm")
|
||||
symbols = language_server.request_document_symbols(utils_path)
|
||||
symbols = language_server.request_document_symbols(utils_path).get_all_symbols_and_roots()
|
||||
formatMessage_symbol = None
|
||||
for sym in symbols[0]:
|
||||
if sym.get("name") == "formatMessage":
|
||||
|
||||
@@ -29,7 +29,7 @@ class TestErlangLanguageServerBasics:
|
||||
"""Test document symbols retrieval for Erlang files."""
|
||||
try:
|
||||
file_path = "hello.erl"
|
||||
symbols_tuple = language_server.request_document_symbols(file_path)
|
||||
symbols_tuple = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
assert isinstance(symbols_tuple, tuple)
|
||||
assert len(symbols_tuple) == 2
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
|
||||
definition_file = "include/records.hrl"
|
||||
|
||||
# Find the user record definition
|
||||
symbols = ls_with_ignored_dirs.request_document_symbols(definition_file)
|
||||
symbols = ls_with_ignored_dirs.request_document_symbols(definition_file).get_all_symbols_and_roots()
|
||||
user_symbol = None
|
||||
for symbol_group in symbols:
|
||||
user_symbol = next((s for s in symbol_group if "user" in s.get("name", "").lower()), None)
|
||||
@@ -107,7 +107,7 @@ def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None:
|
||||
definition_file = "include/records.hrl"
|
||||
|
||||
# Find the user record definition
|
||||
symbols = ls.request_document_symbols(definition_file)
|
||||
symbols = ls.request_document_symbols(definition_file).get_all_symbols_and_roots()
|
||||
user_symbol = None
|
||||
for symbol_group in symbols:
|
||||
user_symbol = next((s for s in symbol_group if "user" in s.get("name", "").lower()), None)
|
||||
@@ -203,7 +203,7 @@ def test_document_symbols_ignores_dirs(ls_with_ignored_dirs: SolidLanguageServer
|
||||
# Try to get symbols from a file in ignored directory (should not find it)
|
||||
try:
|
||||
ignored_file = "ignored_dir/ignored_module.erl"
|
||||
symbols = ls_with_ignored_dirs.request_document_symbols(ignored_file)
|
||||
symbols = ls_with_ignored_dirs.request_document_symbols(ignored_file).get_all_symbols_and_roots()
|
||||
# If we get here, the file was found - symbols should be empty or None
|
||||
if symbols:
|
||||
assert len(symbols) == 0, "Should not find symbols in ignored directory"
|
||||
|
||||
@@ -126,7 +126,7 @@ class TestErlangLanguageServerSymbols:
|
||||
# Test referencing symbols for user record
|
||||
file_path = os.path.join("include", "records.hrl")
|
||||
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
user_symbol = None
|
||||
for symbol_group in symbols:
|
||||
user_symbol = next((s for s in symbol_group if "user" in s.get("name", "")), None)
|
||||
@@ -156,7 +156,7 @@ class TestErlangLanguageServerSymbols:
|
||||
# Test referencing symbols for create_user function
|
||||
file_path = os.path.join("src", "models.erl")
|
||||
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
create_user_symbol = None
|
||||
for symbol_group in symbols:
|
||||
create_user_symbol = next((s for s in symbol_group if "create_user" in s.get("name", "")), None)
|
||||
@@ -418,7 +418,7 @@ class TestErlangLanguageServerSymbols:
|
||||
# Test that we can find references to models module functions in services.erl
|
||||
file_path = os.path.join("src", "models.erl")
|
||||
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
create_user_symbol = None
|
||||
for symbol_group in symbols:
|
||||
create_user_symbol = next((s for s in symbol_group if "create_user" in s.get("name", "")), None)
|
||||
|
||||
@@ -41,12 +41,12 @@ class TestFortranLanguageServer:
|
||||
def test_request_document_symbols(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that document symbols can be retrieved from Fortran files."""
|
||||
# Test main.f90 - should have a program symbol
|
||||
main_symbols, _ = language_server.request_document_symbols("main.f90")
|
||||
main_symbols, _ = language_server.request_document_symbols("main.f90").get_all_symbols_and_roots()
|
||||
program_names = [s.get("name") for s in main_symbols]
|
||||
assert "test_program" in program_names, f"Program 'test_program' not found in main.f90. Found: {program_names}"
|
||||
|
||||
# Test modules/math_utils.f90 - should have module and function symbols
|
||||
module_symbols, _ = language_server.request_document_symbols("modules/math_utils.f90")
|
||||
module_symbols, _ = language_server.request_document_symbols("modules/math_utils.f90").get_all_symbols_and_roots()
|
||||
all_names = [s.get("name") for s in module_symbols]
|
||||
assert "math_utils" in all_names, f"Module 'math_utils' not found. Found: {all_names}"
|
||||
assert "add_numbers" in all_names, f"Function 'add_numbers' not found. Found: {all_names}"
|
||||
@@ -60,7 +60,7 @@ class TestFortranLanguageServer:
|
||||
This tests the LSP textDocument/references capability.
|
||||
"""
|
||||
file_path = "modules/math_utils.f90"
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
# Find the add_numbers function
|
||||
add_numbers_symbol = None
|
||||
@@ -125,7 +125,7 @@ class TestFortranLanguageServer:
|
||||
"""
|
||||
# Get the add_numbers function symbol from math_utils.f90
|
||||
file_path = "modules/math_utils.f90"
|
||||
symbols, _ = language_server.request_document_symbols(file_path)
|
||||
symbols, _ = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
# Find the add_numbers function
|
||||
add_numbers_symbol = None
|
||||
@@ -230,7 +230,7 @@ class TestFortranLanguageServer:
|
||||
fortls returns these as SymbolKind.Class (11) for types and SymbolKind.Interface (5) for interfaces.
|
||||
"""
|
||||
file_path = "modules/geometry.f90"
|
||||
symbols, _ = language_server.request_document_symbols(file_path)
|
||||
symbols, _ = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
# Find type and interface symbols
|
||||
type_names = []
|
||||
|
||||
@@ -19,7 +19,7 @@ class TestGoLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True)
|
||||
def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
|
||||
file_path = os.path.join("main.go")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
helper_symbol = None
|
||||
for sym in symbols[0]:
|
||||
if sym.get("name") == "Helper":
|
||||
|
||||
@@ -34,7 +34,7 @@ class TestHaskellLanguageServer:
|
||||
- All exported functions with correct names
|
||||
- Module structure
|
||||
"""
|
||||
all_symbols, _ = language_server.request_document_symbols("src/Calculator.hs")
|
||||
all_symbols, _ = language_server.request_document_symbols("src/Calculator.hs").get_all_symbols_and_roots()
|
||||
symbol_names = {s["name"] for s in all_symbols}
|
||||
|
||||
# Verify exact set of expected top-level symbols
|
||||
@@ -71,7 +71,7 @@ class TestHaskellLanguageServer:
|
||||
Verifies Serena identifies all helper functions that are imported
|
||||
and used by Calculator module.
|
||||
"""
|
||||
all_symbols, _ = language_server.request_document_symbols("src/Helper.hs")
|
||||
all_symbols, _ = language_server.request_document_symbols("src/Helper.hs").get_all_symbols_and_roots()
|
||||
symbol_names = {s["name"] for s in all_symbols}
|
||||
|
||||
# Verify expected helper functions (module name may also appear)
|
||||
@@ -97,7 +97,7 @@ class TestHaskellLanguageServer:
|
||||
|
||||
Verifies Serena can identify cross-module dependencies.
|
||||
"""
|
||||
all_symbols, _ = language_server.request_document_symbols("app/Main.hs")
|
||||
all_symbols, _ = language_server.request_document_symbols("app/Main.hs").get_all_symbols_and_roots()
|
||||
symbol_names = {s["name"] for s in all_symbols}
|
||||
|
||||
# Main.hs should have the main function
|
||||
|
||||
@@ -25,7 +25,7 @@ class TestJavaLanguageServer:
|
||||
|
||||
# Dynamically determine the correct line/column for the 'Model' class name
|
||||
file_path = os.path.join("src", "main", "java", "test_repo", "Model.java")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
model_symbol = None
|
||||
for sym in symbols[0]:
|
||||
if sym.get("name") == "Model" and sym.get("kind") == 5: # 5 = Class
|
||||
|
||||
@@ -11,7 +11,7 @@ class TestJuliaLanguageServer:
|
||||
"""
|
||||
Test if we can find the top-level symbols in the main.jl file.
|
||||
"""
|
||||
all_symbols, _ = language_server.request_document_symbols("main.jl")
|
||||
all_symbols, _ = language_server.request_document_symbols("main.jl").get_all_symbols_and_roots()
|
||||
symbol_names = {s["name"] for s in all_symbols}
|
||||
assert "calculate_sum" in symbol_names
|
||||
assert "main" in symbol_names
|
||||
|
||||
@@ -25,7 +25,7 @@ class TestKotlinLanguageServer:
|
||||
|
||||
# Dynamically determine the correct line/column for the 'Model' class name
|
||||
file_path = os.path.join("src", "main", "kotlin", "test_repo", "Model.kt")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
model_symbol = None
|
||||
for sym in symbols[0]:
|
||||
print(sym)
|
||||
|
||||
@@ -19,7 +19,7 @@ class TestLuaLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.LUA], indirect=True)
|
||||
def test_find_symbols_in_calculator(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding specific functions in calculator.lua."""
|
||||
symbols = language_server.request_document_symbols("src/calculator.lua")
|
||||
symbols = language_server.request_document_symbols("src/calculator.lua").get_all_symbols_and_roots()
|
||||
|
||||
assert symbols is not None
|
||||
assert len(symbols) > 0
|
||||
@@ -49,7 +49,7 @@ class TestLuaLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.LUA], indirect=True)
|
||||
def test_find_symbols_in_utils(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding specific functions in utils.lua."""
|
||||
symbols = language_server.request_document_symbols("src/utils.lua")
|
||||
symbols = language_server.request_document_symbols("src/utils.lua").get_all_symbols_and_roots()
|
||||
|
||||
assert symbols is not None
|
||||
assert len(symbols) > 0
|
||||
@@ -84,7 +84,7 @@ class TestLuaLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.LUA], indirect=True)
|
||||
def test_find_symbols_in_main(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding functions in main.lua."""
|
||||
symbols = language_server.request_document_symbols("main.lua")
|
||||
symbols = language_server.request_document_symbols("main.lua").get_all_symbols_and_roots()
|
||||
|
||||
assert symbols is not None
|
||||
assert len(symbols) > 0
|
||||
@@ -107,7 +107,7 @@ class TestLuaLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.LUA], indirect=True)
|
||||
def test_cross_file_references_calculator_add(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding cross-file references to calculator.add function."""
|
||||
symbols = language_server.request_document_symbols("src/calculator.lua")
|
||||
symbols = language_server.request_document_symbols("src/calculator.lua").get_all_symbols_and_roots()
|
||||
|
||||
assert symbols is not None
|
||||
symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
|
||||
@@ -163,7 +163,7 @@ class TestLuaLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.LUA], indirect=True)
|
||||
def test_cross_file_references_utils_trim(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding cross-file references to utils.trim function."""
|
||||
symbols = language_server.request_document_symbols("src/utils.lua")
|
||||
symbols = language_server.request_document_symbols("src/utils.lua").get_all_symbols_and_roots()
|
||||
|
||||
assert symbols is not None
|
||||
symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
|
||||
@@ -245,7 +245,7 @@ class TestLuaLanguageServer:
|
||||
def test_references_between_test_and_source(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding references from test files to source files."""
|
||||
# Check if test_calculator.lua references calculator module
|
||||
test_symbols = language_server.request_document_symbols("tests/test_calculator.lua")
|
||||
test_symbols = language_server.request_document_symbols("tests/test_calculator.lua").get_all_symbols_and_roots()
|
||||
|
||||
assert test_symbols is not None
|
||||
assert len(test_symbols) > 0
|
||||
|
||||
@@ -25,7 +25,7 @@ class TestMarkdownLanguageServerBasics:
|
||||
def test_markdown_request_document_symbols(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_document_symbols for markdown files."""
|
||||
# Test getting symbols from README.md
|
||||
all_symbols, _root_symbols = language_server.request_document_symbols("README.md", include_body=False)
|
||||
all_symbols, _root_symbols = language_server.request_document_symbols("README.md").get_all_symbols_and_roots()
|
||||
|
||||
# Extract heading symbols (LSP Symbol Kind 15 is String, but marksman uses kind 15 for headings)
|
||||
# Note: Different markdown LSPs may use different symbol kinds for headings
|
||||
@@ -38,7 +38,7 @@ class TestMarkdownLanguageServerBasics:
|
||||
@pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
|
||||
def test_markdown_request_symbols_from_guide(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test symbol detection in guide.md file."""
|
||||
all_symbols, _root_symbols = language_server.request_document_symbols("guide.md", include_body=False)
|
||||
all_symbols, _root_symbols = language_server.request_document_symbols("guide.md").get_all_symbols_and_roots()
|
||||
|
||||
# At least some headings should be found
|
||||
assert len(all_symbols) > 0, f"Should find headings in guide.md, found {len(all_symbols)}"
|
||||
@@ -46,7 +46,7 @@ class TestMarkdownLanguageServerBasics:
|
||||
@pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
|
||||
def test_markdown_request_symbols_from_api(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test symbol detection in api.md file."""
|
||||
all_symbols, _root_symbols = language_server.request_document_symbols("api.md", include_body=False)
|
||||
all_symbols, _root_symbols = language_server.request_document_symbols("api.md").get_all_symbols_and_roots()
|
||||
|
||||
# Should detect headings from api.md
|
||||
assert len(all_symbols) > 0, f"Should find headings in api.md, found {len(all_symbols)}"
|
||||
@@ -55,7 +55,7 @@ class TestMarkdownLanguageServerBasics:
|
||||
def test_markdown_request_document_symbols_with_body(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_document_symbols with body extraction."""
|
||||
# Test with include_body=True
|
||||
all_symbols, _root_symbols = language_server.request_document_symbols("README.md", include_body=True)
|
||||
all_symbols, _root_symbols = language_server.request_document_symbols("README.md").get_all_symbols_and_roots()
|
||||
|
||||
# Should have found some symbols
|
||||
assert len(all_symbols) > 0, "Should find symbols in README.md"
|
||||
|
||||
@@ -22,7 +22,7 @@ class TestNixLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
|
||||
def test_find_symbols_in_default_nix(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding specific symbols in default.nix."""
|
||||
symbols = language_server.request_document_symbols("default.nix")
|
||||
symbols = language_server.request_document_symbols("default.nix").get_all_symbols_and_roots()
|
||||
|
||||
assert symbols is not None
|
||||
assert len(symbols) > 0
|
||||
@@ -42,7 +42,7 @@ class TestNixLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
|
||||
def test_find_symbols_in_utils(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding symbols in lib/utils.nix."""
|
||||
symbols = language_server.request_document_symbols("lib/utils.nix")
|
||||
symbols = language_server.request_document_symbols("lib/utils.nix").get_all_symbols_and_roots()
|
||||
|
||||
assert symbols is not None
|
||||
assert len(symbols) > 0
|
||||
@@ -58,7 +58,7 @@ class TestNixLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
|
||||
def test_find_symbols_in_flake(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding symbols in flake.nix."""
|
||||
symbols = language_server.request_document_symbols("flake.nix")
|
||||
symbols = language_server.request_document_symbols("flake.nix").get_all_symbols_and_roots()
|
||||
|
||||
assert symbols is not None
|
||||
assert len(symbols) > 0
|
||||
@@ -72,7 +72,7 @@ class TestNixLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
|
||||
def test_find_symbols_in_module(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding symbols in a NixOS module."""
|
||||
symbols = language_server.request_document_symbols("modules/example.nix")
|
||||
symbols = language_server.request_document_symbols("modules/example.nix").get_all_symbols_and_roots()
|
||||
|
||||
assert symbols is not None
|
||||
assert len(symbols) > 0
|
||||
@@ -86,7 +86,7 @@ class TestNixLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
|
||||
def test_find_references_within_file(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding references within the same file."""
|
||||
symbols = language_server.request_document_symbols("default.nix")
|
||||
symbols = language_server.request_document_symbols("default.nix").get_all_symbols_and_roots()
|
||||
|
||||
assert symbols is not None
|
||||
symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
|
||||
@@ -154,7 +154,7 @@ class TestNixLanguageServer:
|
||||
def test_verify_imports_exist(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Verify that our test files have proper imports set up."""
|
||||
# Verify that default.nix imports utils from lib/utils.nix
|
||||
symbols = language_server.request_document_symbols("default.nix")
|
||||
symbols = language_server.request_document_symbols("default.nix").get_all_symbols_and_roots()
|
||||
|
||||
assert symbols is not None
|
||||
symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
|
||||
@@ -164,7 +164,7 @@ class TestNixLanguageServer:
|
||||
assert "makeGreeting" in symbol_names, "makeGreeting should be found in default.nix"
|
||||
|
||||
# Verify lib/utils.nix has the expected structure
|
||||
utils_symbols = language_server.request_document_symbols("lib/utils.nix")
|
||||
utils_symbols = language_server.request_document_symbols("lib/utils.nix").get_all_symbols_and_roots()
|
||||
assert utils_symbols is not None
|
||||
utils_list = utils_symbols[0] if isinstance(utils_symbols, tuple) else utils_symbols
|
||||
utils_names = {sym.get("name") for sym in utils_list if isinstance(sym, dict)}
|
||||
|
||||
@@ -31,7 +31,7 @@ class TestPerlLanguageServer:
|
||||
def test_document_symbols(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that document symbols are correctly identified."""
|
||||
# Request document symbols
|
||||
all_symbols, _ = language_server.request_document_symbols("main.pl", include_body=False)
|
||||
all_symbols, _ = language_server.request_document_symbols("main.pl").get_all_symbols_and_roots()
|
||||
|
||||
assert all_symbols, "Expected to find symbols in main.pl"
|
||||
assert len(all_symbols) > 0, "Expected at least one symbol"
|
||||
|
||||
@@ -26,7 +26,7 @@ class TestLanguageServerBasics:
|
||||
file_path = os.path.join("test_repo", "models.py")
|
||||
# Line 31 contains the User class definition
|
||||
# Use selectionRange only
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
user_symbol = next((s for s in symbols[0] if s.get("name") == "User"), None)
|
||||
if not user_symbol or "selectionRange" not in user_symbol:
|
||||
raise AssertionError("User symbol or its selectionRange not found")
|
||||
@@ -41,7 +41,7 @@ class TestLanguageServerBasics:
|
||||
file_path = os.path.join("test_repo", "models.py")
|
||||
# Line 56 contains the Item class definition
|
||||
# Use selectionRange only
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
item_symbol = next((s for s in symbols[0] if s.get("name") == "Item"), None)
|
||||
if not item_symbol or "selectionRange" not in item_symbol:
|
||||
raise AssertionError("Item symbol or its selectionRange not found")
|
||||
@@ -57,7 +57,7 @@ class TestLanguageServerBasics:
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
# Line 24 contains the get_user method with id parameter
|
||||
# Use selectionRange only
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
get_user_symbol = next((s for s in symbols[0] if s.get("name") == "get_user"), None)
|
||||
if not get_user_symbol or "selectionRange" not in get_user_symbol:
|
||||
raise AssertionError("get_user symbol or its selectionRange not found")
|
||||
@@ -71,7 +71,7 @@ class TestLanguageServerBasics:
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
# Line 15 contains the create_user method definition
|
||||
# Use selectionRange only
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
create_user_symbol = next((s for s in symbols[0] if s.get("name") == "create_user"), None)
|
||||
if not create_user_symbol or "selectionRange" not in create_user_symbol:
|
||||
raise AssertionError("create_user symbol or its selectionRange not found")
|
||||
|
||||
@@ -107,7 +107,7 @@ class TestLanguageServerSymbols:
|
||||
# Test referencing symbols for create_user function
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
# Line 15 contains the create_user function definition
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
create_user_symbol = next((s for s in symbols[0] if s.get("name") == "create_user"), None)
|
||||
if not create_user_symbol or "selectionRange" not in create_user_symbol:
|
||||
raise AssertionError("create_user symbol or its selectionRange not found")
|
||||
@@ -131,7 +131,7 @@ class TestLanguageServerSymbols:
|
||||
# Test referencing symbols for User class
|
||||
file_path = os.path.join("test_repo", "models.py")
|
||||
# Line 31 contains the User class definition
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
user_symbol = next((s for s in symbols[0] if s.get("name") == "User"), None)
|
||||
if not user_symbol or "selectionRange" not in user_symbol:
|
||||
raise AssertionError("User symbol or its selectionRange not found")
|
||||
@@ -152,7 +152,7 @@ class TestLanguageServerSymbols:
|
||||
# Test referencing symbols for id parameter in get_user
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
# Line 24 contains the get_user method with id parameter
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
get_user_symbol = next((s for s in symbols[0] if s.get("name") == "get_user"), None)
|
||||
if not get_user_symbol or "selectionRange" not in get_user_symbol:
|
||||
raise AssertionError("get_user symbol or its selectionRange not found")
|
||||
@@ -390,7 +390,9 @@ class TestLanguageServerSymbols:
|
||||
if "location" in user_management_node and "relativePath" in user_management_node["location"]:
|
||||
user_management_rel_path = user_management_node["location"]["relativePath"]
|
||||
assert user_management_rel_path == os.path.join("examples", "user_management.py")
|
||||
_, user_management_roots = language_server.request_document_symbols(os.path.join("examples", "user_management.py"))
|
||||
_, user_management_roots = language_server.request_document_symbols(
|
||||
os.path.join("examples", "user_management.py")
|
||||
).get_all_symbols_and_roots()
|
||||
assert user_management_roots == user_management_node["children"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@@ -411,7 +413,9 @@ class TestLanguageServerSymbols:
|
||||
if "location" in user_management_node and "relativePath" in user_management_node["location"]:
|
||||
user_management_rel_path = user_management_node["location"]["relativePath"]
|
||||
assert user_management_rel_path == os.path.join("examples", "user_management.py")
|
||||
_, user_management_roots = language_server.request_document_symbols(os.path.join("examples", "user_management.py"))
|
||||
_, user_management_roots = language_server.request_document_symbols(
|
||||
os.path.join("examples", "user_management.py")
|
||||
).get_all_symbols_and_roots()
|
||||
assert user_management_roots == user_management_node["children"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
|
||||
@@ -27,7 +27,7 @@ class TestRLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.R], indirect=True)
|
||||
def test_symbol_retrieval(self, language_server: SolidLanguageServer):
|
||||
"""Test R document symbol extraction."""
|
||||
all_symbols, _root_symbols = language_server.request_document_symbols(os.path.join("R", "utils.R"))
|
||||
all_symbols, _root_symbols = language_server.request_document_symbols(os.path.join("R", "utils.R")).get_all_symbols_and_roots()
|
||||
|
||||
# Should find the three exported functions
|
||||
function_symbols = [s for s in all_symbols if s.get("kind") == 12] # Function kind
|
||||
|
||||
@@ -21,7 +21,7 @@ class TestRegoLanguageServer:
|
||||
def test_request_document_symbols_authz(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that document symbols can be retrieved from authz.rego."""
|
||||
file_path = os.path.join("policies", "authz.rego")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
assert symbols is not None
|
||||
assert len(symbols) > 0
|
||||
@@ -40,7 +40,7 @@ class TestRegoLanguageServer:
|
||||
def test_request_document_symbols_helpers(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that document symbols can be retrieved from helpers.rego."""
|
||||
file_path = os.path.join("utils", "helpers.rego")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
assert symbols is not None
|
||||
assert len(symbols) > 0
|
||||
@@ -71,7 +71,7 @@ class TestRegoLanguageServer:
|
||||
file_path = os.path.join("policies", "authz.rego")
|
||||
|
||||
# Get document symbols
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
|
||||
|
||||
# Find the is_admin symbol which references admin_roles
|
||||
@@ -98,7 +98,7 @@ class TestRegoLanguageServer:
|
||||
file_path = os.path.join("policies", "authz.rego")
|
||||
|
||||
# Get document symbols
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
|
||||
|
||||
# Find the allow symbol
|
||||
@@ -123,7 +123,7 @@ class TestRegoLanguageServer:
|
||||
def test_find_symbols_validation(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding symbols in validation.rego which has imports."""
|
||||
file_path = os.path.join("policies", "validation.rego")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
assert symbols is not None
|
||||
assert len(symbols) > 0
|
||||
|
||||
@@ -20,7 +20,7 @@ class TestRubyLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
|
||||
def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
|
||||
file_path = os.path.join("main.rb")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
helper_symbol = None
|
||||
for sym in symbols[0]:
|
||||
if sym.get("name") == "helper_function":
|
||||
|
||||
@@ -89,7 +89,7 @@ class TestRubyLanguageServerSymbols:
|
||||
"""Test request_containing_symbol for a module context."""
|
||||
# Test that we can find the Services module in document symbols
|
||||
file_path = os.path.join("services.rb")
|
||||
symbols, _roots = language_server.request_document_symbols(file_path)
|
||||
symbols, _roots = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
# Verify Services module appears in document symbols
|
||||
services_module = None
|
||||
@@ -141,7 +141,7 @@ class TestRubyLanguageServerSymbols:
|
||||
# Test referencing symbols for create_user method
|
||||
file_path = os.path.join("services.rb")
|
||||
# Line containing the create_user method definition
|
||||
symbols, _roots = language_server.request_document_symbols(file_path)
|
||||
symbols, _roots = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
create_user_symbol = None
|
||||
|
||||
# Find create_user method in the document symbols (Ruby returns flat list)
|
||||
@@ -169,7 +169,7 @@ class TestRubyLanguageServerSymbols:
|
||||
# Test referencing symbols for User class
|
||||
file_path = os.path.join("models.rb")
|
||||
# Find User class in document symbols
|
||||
symbols, _roots = language_server.request_document_symbols(file_path)
|
||||
symbols, _roots = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
user_symbol = None
|
||||
|
||||
for symbol in symbols:
|
||||
@@ -285,7 +285,7 @@ class TestRubyLanguageServerSymbols:
|
||||
def test_document_symbols_detailed(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test document symbols for detailed Ruby file structure."""
|
||||
file_path = os.path.join("models.rb")
|
||||
symbols, roots = language_server.request_document_symbols(file_path)
|
||||
symbols, roots = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
# Verify we have symbols
|
||||
assert len(symbols) > 0 or len(roots) > 0
|
||||
@@ -310,7 +310,7 @@ class TestRubyLanguageServerSymbols:
|
||||
def test_module_and_class_hierarchy(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test symbol detection for modules and nested class hierarchies."""
|
||||
file_path = os.path.join("nested.rb")
|
||||
symbols, roots = language_server.request_document_symbols(file_path)
|
||||
symbols, roots = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
# Verify we can detect the nested structure
|
||||
assert len(symbols) > 0 or len(roots) > 0
|
||||
@@ -363,7 +363,7 @@ class TestRubyLanguageServerSymbols:
|
||||
# Test referencing symbols for a method parameter in get_user method
|
||||
file_path = os.path.join("services.rb")
|
||||
# Find get_user method and test parameter references
|
||||
symbols, _roots = language_server.request_document_symbols(file_path)
|
||||
symbols, _roots = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
get_user_symbol = None
|
||||
|
||||
for symbol in symbols:
|
||||
|
||||
@@ -31,7 +31,7 @@ class TestRust2024EditionLanguageServer:
|
||||
def test_find_references_raw(self) -> None:
|
||||
# Test finding references to the 'add' function defined in main.rs
|
||||
file_path = os.path.join("src", "main.rs")
|
||||
symbols = self.language_server.request_document_symbols(file_path)
|
||||
symbols = self.language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
add_symbol = None
|
||||
for sym in symbols[0]:
|
||||
if sym.get("name") == "add":
|
||||
@@ -53,7 +53,7 @@ class TestRust2024EditionLanguageServer:
|
||||
def test_find_referencing_symbols_multiply(self) -> None:
|
||||
# Find references to 'multiply' function defined in lib.rs
|
||||
file_path = os.path.join("src", "lib.rs")
|
||||
symbols = self.language_server.request_document_symbols(file_path)
|
||||
symbols = self.language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
multiply_symbol = None
|
||||
for sym in symbols[0]:
|
||||
if sym.get("name") == "multiply":
|
||||
@@ -69,7 +69,7 @@ class TestRust2024EditionLanguageServer:
|
||||
def test_find_calculator_struct_and_impl(self) -> None:
|
||||
# Test finding the Calculator struct and its impl block
|
||||
file_path = os.path.join("src", "lib.rs")
|
||||
symbols = self.language_server.request_document_symbols(file_path)
|
||||
symbols = self.language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
# Find the Calculator struct
|
||||
calculator_struct = None
|
||||
|
||||
@@ -13,7 +13,7 @@ class TestRustLanguageServer:
|
||||
def test_find_references_raw(self, language_server: SolidLanguageServer) -> None:
|
||||
# Directly test the request_references method for the add function
|
||||
file_path = os.path.join("src", "lib.rs")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
add_symbol = None
|
||||
for sym in symbols[0]:
|
||||
if sym.get("name") == "add":
|
||||
@@ -37,7 +37,7 @@ class TestRustLanguageServer:
|
||||
def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
|
||||
# Find references to 'add' defined in lib.rs, should be referenced from main.rs
|
||||
file_path = os.path.join("src", "lib.rs")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
add_symbol = None
|
||||
for sym in symbols[0]:
|
||||
if sym.get("name") == "add":
|
||||
|
||||
@@ -29,7 +29,7 @@ def scala_ls():
|
||||
|
||||
def test_scala_document_symbols(scala_ls):
|
||||
"""Test document symbols for Main.scala"""
|
||||
symbols, _ = scala_ls.request_document_symbols(MAIN_FILE_PATH)
|
||||
symbols, _ = scala_ls.request_document_symbols(MAIN_FILE_PATH).get_all_symbols_and_roots()
|
||||
symbol_names = [s["name"] for s in symbols]
|
||||
assert symbol_names[0] == "com.example"
|
||||
assert symbol_names[1] == "Main"
|
||||
|
||||
@@ -88,7 +88,7 @@ class TestSwiftLanguageServerBasics:
|
||||
|
||||
# First, let's check if Utils is used anywhere (it might not be in this simple test)
|
||||
# We'll test goto_definition on Utils struct itself
|
||||
symbols = language_server.request_document_symbols(utils_file)
|
||||
symbols = language_server.request_document_symbols(utils_file).get_all_symbols_and_roots()
|
||||
utils_symbol = next((s for s in symbols[0] if s.get("name") == "Utils"), None)
|
||||
|
||||
sel_start = utils_symbol["selectionRange"]["start"]
|
||||
@@ -104,7 +104,7 @@ class TestSwiftLanguageServerBasics:
|
||||
"""Test request_references on the Calculator class."""
|
||||
# Get references to the Calculator class in main.swift
|
||||
file_path = os.path.join("src", "main.swift")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
calculator_symbol = next((s for s in symbols[0] if s.get("name") == "Calculator"), None)
|
||||
|
||||
@@ -126,7 +126,7 @@ class TestSwiftLanguageServerBasics:
|
||||
"""Test request_references on the User struct."""
|
||||
# Get references to the User struct in main.swift
|
||||
file_path = os.path.join("src", "main.swift")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
user_symbol = next((s for s in symbols[0] if s.get("name") == "User"), None)
|
||||
|
||||
@@ -147,7 +147,7 @@ class TestSwiftLanguageServerBasics:
|
||||
"""Test request_references on the Utils struct."""
|
||||
# Get references to the Utils struct in utils.swift
|
||||
file_path = os.path.join("src", "utils.swift")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
utils_symbol = next((s for s in symbols[0] if s.get("name") == "Utils"), None)
|
||||
if not utils_symbol or "selectionRange" not in utils_symbol:
|
||||
raise AssertionError("Utils symbol or its selectionRange not found")
|
||||
|
||||
@@ -21,7 +21,7 @@ class TestLanguageServerBasics:
|
||||
# Simple test to verify the language server is working
|
||||
file_path = "main.tf"
|
||||
# Just try to get document symbols - this should work without hanging
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
assert len(symbols) > 0, "Should find at least some symbols in main.tf"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.TERRAFORM], indirect=True)
|
||||
@@ -30,7 +30,7 @@ class TestLanguageServerBasics:
|
||||
# Get references to an aws_instance resource in main.tf
|
||||
file_path = "main.tf"
|
||||
# Find aws_instance resources
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
aws_instance_symbol = next((s for s in symbols[0] if s.get("name") == 'resource "aws_instance" "web_server"'), None)
|
||||
if not aws_instance_symbol or "selectionRange" not in aws_instance_symbol:
|
||||
raise AssertionError("aws_instance symbol or its selectionRange not found")
|
||||
@@ -44,7 +44,7 @@ class TestLanguageServerBasics:
|
||||
# Get references to a variable in variables.tf
|
||||
file_path = "variables.tf"
|
||||
# Find variable definitions
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
var_symbol = next((s for s in symbols[0] if s.get("name") == 'variable "instance_type"'), None)
|
||||
if not var_symbol or "selectionRange" not in var_symbol:
|
||||
raise AssertionError("variable symbol or its selectionRange not found")
|
||||
|
||||
@@ -19,7 +19,7 @@ class TestTypescriptLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.TYPESCRIPT], indirect=True)
|
||||
def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
|
||||
file_path = os.path.join("index.ts")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
helper_symbol = None
|
||||
for sym in symbols[0]:
|
||||
if sym.get("name") == "helperFunction":
|
||||
|
||||
@@ -30,7 +30,7 @@ class TestZigLanguageServer:
|
||||
def test_find_symbols_in_main(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding specific symbols in main.zig."""
|
||||
file_path = os.path.join("src", "main.zig")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
assert symbols is not None
|
||||
assert len(symbols) > 0
|
||||
@@ -47,7 +47,7 @@ class TestZigLanguageServer:
|
||||
def test_find_symbols_in_calculator(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding Calculator struct and its methods."""
|
||||
file_path = os.path.join("src", "calculator.zig")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
assert symbols is not None
|
||||
assert len(symbols) > 0
|
||||
@@ -90,7 +90,7 @@ class TestZigLanguageServer:
|
||||
def test_find_symbols_in_math_utils(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding functions in math_utils.zig."""
|
||||
file_path = os.path.join("src", "math_utils.zig")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
assert symbols is not None
|
||||
assert len(symbols) > 0
|
||||
@@ -106,7 +106,7 @@ class TestZigLanguageServer:
|
||||
def test_find_references_within_file(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding references within the same file."""
|
||||
file_path = os.path.join("src", "calculator.zig")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
|
||||
symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
|
||||
|
||||
@@ -163,7 +163,7 @@ class TestZigLanguageServer:
|
||||
time.sleep(1)
|
||||
|
||||
# Find Calculator struct
|
||||
symbols = language_server.request_document_symbols(os.path.join("src", "calculator.zig"))
|
||||
symbols = language_server.request_document_symbols(os.path.join("src", "calculator.zig")).get_all_symbols_and_roots()
|
||||
symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
|
||||
|
||||
calculator_symbol = None
|
||||
@@ -207,7 +207,7 @@ class TestZigLanguageServer:
|
||||
"""
|
||||
# Find references to Calculator from calculator.zig
|
||||
file_path = os.path.join("src", "calculator.zig")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
|
||||
|
||||
calculator_symbol = None
|
||||
@@ -291,7 +291,7 @@ class TestZigLanguageServer:
|
||||
def test_verify_cross_file_imports(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Verify that our test files have proper cross-file imports."""
|
||||
# Verify main.zig imports
|
||||
main_symbols = language_server.request_document_symbols(os.path.join("src", "main.zig"))
|
||||
main_symbols = language_server.request_document_symbols(os.path.join("src", "main.zig")).get_all_symbols_and_roots()
|
||||
assert main_symbols is not None
|
||||
main_list = main_symbols[0] if isinstance(main_symbols, tuple) else main_symbols
|
||||
main_names = {sym.get("name") for sym in main_list if isinstance(sym, dict)}
|
||||
@@ -301,14 +301,14 @@ class TestZigLanguageServer:
|
||||
assert "greeting" in main_names, "greeting function should be in main.zig"
|
||||
|
||||
# Verify calculator.zig exports Calculator
|
||||
calc_symbols = language_server.request_document_symbols(os.path.join("src", "calculator.zig"))
|
||||
calc_symbols = language_server.request_document_symbols(os.path.join("src", "calculator.zig")).get_all_symbols_and_roots()
|
||||
assert calc_symbols is not None
|
||||
calc_list = calc_symbols[0] if isinstance(calc_symbols, tuple) else calc_symbols
|
||||
calc_names = {sym.get("name") for sym in calc_list if isinstance(sym, dict)}
|
||||
assert "Calculator" in calc_names, "Calculator struct should be in calculator.zig"
|
||||
|
||||
# Verify math_utils.zig exports functions
|
||||
math_symbols = language_server.request_document_symbols(os.path.join("src", "math_utils.zig"))
|
||||
math_symbols = language_server.request_document_symbols(os.path.join("src", "math_utils.zig")).get_all_symbols_and_roots()
|
||||
assert math_symbols is not None
|
||||
math_list = math_symbols[0] if isinstance(math_symbols, tuple) else math_symbols
|
||||
math_names = {sym.get("name") for sym in math_list if isinstance(sym, dict)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
revision = 2
|
||||
requires-python = "==3.11.*"
|
||||
|
||||
[[package]]
|
||||
@@ -1524,6 +1524,22 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyinstrument"
|
||||
version = "5.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/80/ce/824ee634994e612156f7b84eaf50b8523c676ebfed8d8dd12939a82f4c15/pyinstrument-5.1.1.tar.gz", hash = "sha256:bc401cda990b3c1cfe8e0e0473cbd605df3c63b73478a89ac4ab108f2184baa8", size = 264730, upload-time = "2025-08-12T11:35:43.426Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/52/65/dc0fcc6122e6f484ffa48260d1023e1fe90e53da8d69a7de656205af91ba/pyinstrument-5.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5272cff6eea21163b2105f6a80c907315e0f567720621e6d5672dc01bf71ee48", size = 130287, upload-time = "2025-08-12T11:34:32.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/96/8a6cab312342f1ad7322b459dcbdb2e041251b7bbe4c1d0dd38ccbf2ae20/pyinstrument-5.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d4e7dc5a4aee37a44ff2e63db3127f2044dd95edcae240cb95915adbf223d4be", size = 122879, upload-time = "2025-08-12T11:34:33.506Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/89/1ad1ae703951832a34ec6b32354c6df1d0690b0333b7b8396c92afcdc23e/pyinstrument-5.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:602d55121df1d88aeb6d8ebc801597fdcb9718f78d602ae81458d65c56f25d24", size = 146412, upload-time = "2025-08-12T11:34:34.975Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/70/588ec07eeaa5d5d4aff2d47c8010379e9b39a7aee7e3fda625a0ce4f5838/pyinstrument-5.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63b5a788ff955e0597bc95463e64d5fa3747017524fdc02a0f5d12d5117cf2b9", size = 144885, upload-time = "2025-08-12T11:34:36.546Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/8f/119ad44d454edb927434bb53d8a69904ecaa47ec56f95c688c557a9590de/pyinstrument-5.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7f66038ec55a12e5510689240cdc745f8e98c90b93363f745106976e5cfb7397", size = 145530, upload-time = "2025-08-12T11:34:37.726Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/31/d53b20d1967ba78574807c5940251c0f238e8e3515ee9c4f20207eb09199/pyinstrument-5.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:110a08b6e7fa9542eb37c337e79467913d364a03bc2062f85566ba96bc82f54e", size = 144708, upload-time = "2025-08-12T11:34:38.855Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/18/78ccb35dd265e83c0b7404a501004387b2e2390a0a44b6e4a004307931d1/pyinstrument-5.1.1-cp311-cp311-win32.whl", hash = "sha256:a223d5e9226ccede5bf2fbd4d13ce0aeb5120501b633ba85290ed94df37d3623", size = 124151, upload-time = "2025-08-12T11:34:40.392Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/c0/3eb94aa2c2f0b8d49e290d4df662e21e707eb4a23f3b938239634fbfdc17/pyinstrument-5.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:16ce582a7b56287d338a8b59688458341aab5c6abda970ba50b2f7b3fd69f89d", size = 124942, upload-time = "2025-08-12T11:34:41.564Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyright"
|
||||
version = "1.1.403"
|
||||
@@ -1875,6 +1891,7 @@ dev = [
|
||||
{ name = "mypy" },
|
||||
{ name = "nbsphinx" },
|
||||
{ name = "poethepoet" },
|
||||
{ name = "pyinstrument" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-xdist" },
|
||||
{ name = "ruff" },
|
||||
@@ -1912,6 +1929,7 @@ requires-dist = [
|
||||
{ name = "poethepoet", marker = "extra == 'dev'", specifier = ">=0.20.0" },
|
||||
{ name = "psutil", specifier = ">=7.0.0" },
|
||||
{ name = "pydantic", specifier = ">=2.10.6" },
|
||||
{ name = "pyinstrument", marker = "extra == 'dev'" },
|
||||
{ name = "pyright", specifier = ">=1.1.396,<2" },
|
||||
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.2" },
|
||||
{ name = "pytest-xdist", marker = "extra == 'dev'", specifier = ">=3.5.0" },
|
||||
|
||||
Reference in new issue
Block a user