mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-15 12:14:52 +00:00
Merge pull request #1243 from oraios/ls-name-normalisation
Improve LS symbol name normalisation handling
This commit is contained in:
3 files changed
+82
-77
No files matched your search
@@ -18,7 +18,7 @@ from overrides import override
|
||||
|
||||
from solidlsp import ls_types
|
||||
from solidlsp.language_servers.common import quote_windows_path
|
||||
from solidlsp.ls import DocumentSymbols, LSPFileBuffer, SolidLanguageServer
|
||||
from solidlsp.ls import DocumentSymbols, LSPFileBuffer, RawDocumentSymbol, SolidLanguageServer
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_types import SymbolKind, UnifiedSymbolInformation
|
||||
from solidlsp.ls_utils import FileUtils
|
||||
@@ -1007,35 +1007,25 @@ class ALLanguageServer(SolidLanguageServer):
|
||||
# Get symbols from parent implementation
|
||||
document_symbols = super().request_document_symbols(relative_file_path, file_buffer=file_buffer)
|
||||
|
||||
# Normalize names by stripping AL object metadata, storing originals for hover
|
||||
def normalize_name(symbol: UnifiedSymbolInformation) -> None:
|
||||
original_name = symbol["name"]
|
||||
normalized_name = self._extract_al_display_name(original_name)
|
||||
|
||||
# Store original name if it was normalized (for hover injection)
|
||||
# Only store if we have valid position data to avoid false matches at (0, 0)
|
||||
if original_name != normalized_name:
|
||||
sel_range = symbol.get("selectionRange")
|
||||
if sel_range:
|
||||
start = sel_range.get("start")
|
||||
if start and "line" in start and "character" in start:
|
||||
line = start["line"]
|
||||
char = start["character"]
|
||||
self._al_original_names[(relative_file_path, line, char)] = original_name
|
||||
|
||||
symbol["name"] = normalized_name
|
||||
|
||||
# Process children recursively
|
||||
if symbol.get("children"):
|
||||
for child in symbol["children"]:
|
||||
normalize_name(child)
|
||||
|
||||
# Apply to all root symbols
|
||||
for sym in document_symbols.root_symbols:
|
||||
normalize_name(sym)
|
||||
|
||||
return document_symbols
|
||||
|
||||
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
|
||||
original_name = symbol["name"]
|
||||
normalized_name = self._extract_al_display_name(original_name)
|
||||
|
||||
# Store original name if it was normalized (for hover injection)
|
||||
# Only store if we have valid position data to avoid false matches at (0, 0)
|
||||
if original_name != normalized_name:
|
||||
sel_range = symbol.get("selectionRange")
|
||||
if sel_range:
|
||||
start = sel_range.get("start") # type: ignore
|
||||
if start and "line" in start and "character" in start:
|
||||
line = start["line"]
|
||||
char = start["character"]
|
||||
self._al_original_names[(relative_file_path, line, char)] = original_name
|
||||
|
||||
return normalized_name
|
||||
|
||||
@override
|
||||
def request_hover(
|
||||
self, relative_file_path: str, line: int, column: int, file_buffer: LSPFileBuffer | None = None
|
||||
|
||||
@@ -7,7 +7,7 @@ import os
|
||||
import platform
|
||||
import shutil
|
||||
import threading
|
||||
from collections.abc import Iterable
|
||||
from collections.abc import Hashable, Iterable
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
@@ -15,10 +15,15 @@ from typing import Any, cast
|
||||
from overrides import override
|
||||
|
||||
from serena.util.dotnet import DotNETUtil
|
||||
from solidlsp.ls import DocumentSymbols, LanguageServerDependencyProvider, LSPFileBuffer, SolidLanguageServer
|
||||
from solidlsp.ls import (
|
||||
LanguageServerDependencyProvider,
|
||||
LSPFileBuffer,
|
||||
RawDocumentSymbol,
|
||||
SolidLanguageServer,
|
||||
)
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_exceptions import SolidLSPException
|
||||
from solidlsp.ls_types import Hover, UnifiedSymbolInformation
|
||||
from solidlsp.ls_types import Hover
|
||||
from solidlsp.ls_utils import FileUtils, PathUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams, InitializeResult
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
@@ -218,28 +223,6 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["bin", "obj", "packages", ".vs"]
|
||||
|
||||
@override
|
||||
def request_document_symbols(self, relative_file_path: str, file_buffer: Any = None) -> DocumentSymbols:
|
||||
"""
|
||||
Override to normalize Roslyn symbol names and cache originals.
|
||||
|
||||
Roslyn 5.5.0+ returns symbol names with type annotations:
|
||||
- Properties: "Name : string"
|
||||
- Methods: "Add(int, int) : int"
|
||||
|
||||
This method:
|
||||
1. Normalizes names to base form ("Name", "Add")
|
||||
2. Caches original names for rich information display
|
||||
3. Populates LSP spec's 'detail' field with type/signature info
|
||||
"""
|
||||
symbols = super().request_document_symbols(relative_file_path, file_buffer)
|
||||
|
||||
# Normalize all symbols recursively
|
||||
for symbol in symbols.iter_symbols():
|
||||
self._normalize_symbol_name(symbol, relative_file_path)
|
||||
|
||||
return symbols
|
||||
|
||||
@override
|
||||
def request_hover(self, relative_file_path: str, line: int, column: int, file_buffer: LSPFileBuffer | None = None) -> Hover | None:
|
||||
"""
|
||||
@@ -265,12 +248,20 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
|
||||
return hover
|
||||
|
||||
def _normalize_symbol_name(self, symbol: UnifiedSymbolInformation, relative_file_path: str) -> None:
|
||||
"""
|
||||
Normalize a single symbol's name and cache the original.
|
||||
Processes children recursively.
|
||||
"""
|
||||
original_name = symbol.get("name", "")
|
||||
def _document_symbols_cache_fingerprint(self) -> Hashable | None:
|
||||
normalize_symbol_name_version = 1
|
||||
return normalize_symbol_name_version
|
||||
|
||||
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
|
||||
# Roslyn 5.5.0+ returns symbol names with type annotations:
|
||||
# - Properties: "Name : string"
|
||||
# - Methods: "Add(int, int) : int"
|
||||
#
|
||||
# This method:
|
||||
# 1. Normalizes names to base form ("Name", "Add")
|
||||
# 2. Caches original names for rich information display
|
||||
# 3. Populates LSP spec's 'detail' field with type/signature info
|
||||
original_name = symbol["name"]
|
||||
|
||||
# Extract base name and type/signature info
|
||||
normalized_name, type_info = self._extract_base_name_and_type(original_name)
|
||||
@@ -279,24 +270,18 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
if original_name != normalized_name:
|
||||
sel_range = symbol.get("selectionRange")
|
||||
if sel_range:
|
||||
start = sel_range.get("start")
|
||||
start = sel_range.get("start") # type: ignore
|
||||
if start and "line" in start and "character" in start:
|
||||
line = start["line"]
|
||||
char = start["character"]
|
||||
cache_key = (relative_file_path, line, char)
|
||||
self._original_symbol_names[cache_key] = original_name
|
||||
|
||||
# Populate LSP spec's 'detail' field with type/signature information
|
||||
# Populate 'detail' field with type/signature information (for UnifiedSymbolInformation)
|
||||
if type_info and "detail" not in symbol:
|
||||
symbol["detail"] = type_info
|
||||
symbol["detail"] = type_info # type: ignore
|
||||
|
||||
# Update the symbol name
|
||||
symbol["name"] = normalized_name
|
||||
|
||||
# Process children recursively
|
||||
children = symbol.get("children", [])
|
||||
for child in children:
|
||||
self._normalize_symbol_name(child, relative_file_path)
|
||||
return normalized_name
|
||||
|
||||
@staticmethod
|
||||
def _extract_base_name_and_type(roslyn_name: str) -> tuple[str, str]:
|
||||
|
||||
+38
-8
@@ -48,7 +48,13 @@ from solidlsp.lsp_protocol_handler.server import (
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
from solidlsp.util.cache import load_cache, save_cache
|
||||
|
||||
GenericDocumentSymbol = Union[LSPTypes.DocumentSymbol, LSPTypes.SymbolInformation, ls_types.UnifiedSymbolInformation]
|
||||
RawDocumentSymbol = Union[DocumentSymbol, SymbolInformation]
|
||||
"""
|
||||
Type alias for the raw symbol information returned by a language server in response to a
|
||||
`textDocument/documentSymbol` request.
|
||||
The `DocumentSymbol` is the preferred type, but the legacy type `SymbolInformation` is also still used.
|
||||
"""
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_debug_enabled = log.isEnabledFor(logging.DEBUG)
|
||||
@@ -211,7 +217,7 @@ class SymbolBodyFactory:
|
||||
def __init__(self, file_buffer: LSPFileBuffer):
|
||||
self._lines = file_buffer.split_lines()
|
||||
|
||||
def create_symbol_body(self, symbol: GenericDocumentSymbol) -> SymbolBody:
|
||||
def create_symbol_body(self, symbol: UnifiedSymbolInformation) -> SymbolBody:
|
||||
existing_body = symbol.get("body", None)
|
||||
if existing_body and isinstance(existing_body, SymbolBody):
|
||||
return existing_body
|
||||
@@ -1262,7 +1268,7 @@ class SolidLanguageServer(ABC):
|
||||
The returned symbols are considered "raw document symbols" (in contrast to processed symbols returned by `request_document_symbols`).
|
||||
|
||||
NOTE: This method can be overridden in subclasses to post-process the raw results.
|
||||
When doing so, be sure to update the init parameter `cache_version_raw_document_symbols`
|
||||
When doing so after the initial implementation, be sure to update the init parameter `cache_version_raw_document_symbols`
|
||||
to a different version (add 1) to ensure that all caches are invalidated appropriately.
|
||||
IMPORTANT: Since rebuilding the raw document symbol cache from the language server results
|
||||
is potentially expensive, prefer overriding the `request_document_symbols` method
|
||||
@@ -1312,13 +1318,30 @@ class SolidLanguageServer(ABC):
|
||||
with self._open_file_context(relative_file_path, file_buffer=file_data) as fd:
|
||||
return get_raw_document_symbols(fd)
|
||||
|
||||
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
|
||||
"""
|
||||
Normalizes the name of the given symbol, e.g. by removing parameter lists from method symbols.
|
||||
|
||||
Override this method in subclasses to implement language-specific normalization logic.
|
||||
NOTE: When changing the override of this method after the initial LS implementation,
|
||||
be sure to also override `_document_symbols_cache_fingerprint` in order to ensure that
|
||||
the caches are invalidated appropriately.
|
||||
|
||||
:param symbol: the symbol
|
||||
:param relative_file_path: the relative path of the file the symbol is located in
|
||||
:return: the normalized name of the symbol
|
||||
"""
|
||||
# the default implementation does not change the name
|
||||
return symbol["name"]
|
||||
|
||||
def request_document_symbols(self, relative_file_path: str, file_buffer: LSPFileBuffer | None = None) -> DocumentSymbols:
|
||||
"""
|
||||
Retrieves the collection of symbols in the given file.
|
||||
|
||||
NOTE: This method can be overridden in subclasses to post-process the results.
|
||||
When doing so, be sure to also override `_document_symbols_cache_fingerprint`
|
||||
When doing so after the initial LS implementation, be sure to also override `_document_symbols_cache_fingerprint`
|
||||
to ensure that the caches are invalidated appropriately.
|
||||
DO NOT override this method to modify symbol names; override `_normalize_symbol_name` instead.
|
||||
|
||||
:param relative_file_path: The relative path of the file that has the symbols
|
||||
:param file_buffer: an optional file buffer if the file is already opened.
|
||||
@@ -1362,7 +1385,7 @@ class SolidLanguageServer(ABC):
|
||||
|
||||
body_factory = SymbolBodyFactory(file_data)
|
||||
|
||||
def convert_to_unified_symbol(original_symbol_dict: GenericDocumentSymbol) -> ls_types.UnifiedSymbolInformation:
|
||||
def convert_to_unified_symbol(original_symbol_dict: RawDocumentSymbol) -> 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).
|
||||
@@ -1403,16 +1426,23 @@ class SolidLanguageServer(ABC):
|
||||
return item
|
||||
|
||||
def convert_symbols_with_common_parent(
|
||||
symbols: list[DocumentSymbol] | list[SymbolInformation] | list[UnifiedSymbolInformation],
|
||||
symbols: list[DocumentSymbol] | list[SymbolInformation],
|
||||
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.
|
||||
"""
|
||||
# apply name normalization and count occurrences of each symbol name
|
||||
total_name_counts: dict[str, int] = defaultdict(lambda: 0)
|
||||
for symbol in symbols:
|
||||
total_name_counts[symbol["name"]] += 1
|
||||
name = self._normalize_symbol_name(symbol, relative_file_path=relative_file_path)
|
||||
symbol["name"] = name
|
||||
total_name_counts[name] += 1
|
||||
|
||||
# convert symbols to the unified representation and
|
||||
# * add overload indices where necessary
|
||||
# * ensure that the "parent" field is set correctly
|
||||
name_counts: dict[str, int] = defaultdict(lambda: 0)
|
||||
unified_symbols = []
|
||||
for symbol in symbols:
|
||||
@@ -1727,7 +1757,7 @@ class SolidLanguageServer(ABC):
|
||||
|
||||
def create_symbol_body(
|
||||
self,
|
||||
symbol: ls_types.UnifiedSymbolInformation | LSPTypes.SymbolInformation,
|
||||
symbol: ls_types.UnifiedSymbolInformation,
|
||||
factory: SymbolBodyFactory | None = None,
|
||||
) -> SymbolBody:
|
||||
if factory is None:
|
||||
|
||||
Reference in new issue
Block a user