SolidLanguageServer: Support name normalisation in request_document_symbols via new method

* Introduce method _normalize_symbol_name to support name transformation
* Apply in CSharpLanguageServer, fixing old override of request_document_symbols
  not accounting for overload handling (invalidating high-level cache accordingly)
* Apply in ALLanguageServer
This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2026-03-29 17:37:57 +02:00
1 parent c184462938
commit 97a6845c78
3 files changed
+71 -72

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, GenericDocumentSymbol, LSPFileBuffer, 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: GenericDocumentSymbol, 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 (
GenericDocumentSymbol,
LanguageServerDependencyProvider,
LSPFileBuffer,
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: GenericDocumentSymbol, 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]:
+27 -3
View File
@@ -1262,7 +1262,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 +1312,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: GenericDocumentSymbol, 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.
@@ -1410,9 +1427,16 @@ class SolidLanguageServer(ABC):
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: