mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-14 00:16:57 +00:00
Fix markdown heading symbol recognition in Marksman LSP
Marksman returns all headings (h1-h6) with SymbolKind.String (15), which causes them to be treated as low-level symbols and filtered out of get_symbols_overview results. Override request_document_symbols in Marksman to remap SymbolKind.String to SymbolKind.Namespace, which is semantically appropriate for headings (named sections containing other content) and ensures they appear in symbol overviews.
This commit is contained in:
1 parent
8375da7dc9
commit
003a9e2a6d
2 files changed
+66
-6
No files matched your search
@@ -9,8 +9,15 @@ import pathlib
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
|
||||
from solidlsp.ls import (
|
||||
DocumentSymbols,
|
||||
LanguageServerDependencyProvider,
|
||||
LanguageServerDependencyProviderSinglePath,
|
||||
LSPFileBuffer,
|
||||
SolidLanguageServer,
|
||||
)
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_types import SymbolKind, UnifiedSymbolInformation
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
@@ -106,6 +113,29 @@ class Marksman(SolidLanguageServer):
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["node_modules", ".obsidian", ".vitepress", ".vuepress"]
|
||||
|
||||
@override
|
||||
def request_document_symbols(self, relative_file_path: str, file_buffer: LSPFileBuffer | None = None) -> DocumentSymbols:
|
||||
"""Override to remap Marksman's heading symbol kinds from String to Namespace.
|
||||
|
||||
Marksman LSP returns all markdown headings (h1-h6) with SymbolKind.String (15).
|
||||
This is problematic because String (15) >= Variable (13), so headings are
|
||||
classified as "low-level" and filtered out of symbol overviews.
|
||||
Remapping to Namespace (3) fixes this and is semantically appropriate
|
||||
(headings are named sections containing other content).
|
||||
"""
|
||||
document_symbols = super().request_document_symbols(relative_file_path, file_buffer=file_buffer)
|
||||
|
||||
def remap_heading_kinds(symbol: UnifiedSymbolInformation) -> None:
|
||||
if symbol["kind"] == SymbolKind.String:
|
||||
symbol["kind"] = SymbolKind.Namespace
|
||||
for child in symbol.get("children", []):
|
||||
remap_heading_kinds(child)
|
||||
|
||||
for sym in document_symbols.root_symbols:
|
||||
remap_heading_kinds(sym)
|
||||
|
||||
return document_symbols
|
||||
|
||||
@staticmethod
|
||||
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
|
||||
@@ -7,8 +7,10 @@ like request_document_symbols using the markdown test repository.
|
||||
|
||||
import pytest
|
||||
|
||||
from serena.symbol import LanguageServerSymbol
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
|
||||
|
||||
@pytest.mark.markdown
|
||||
@@ -24,17 +26,19 @@ class TestMarkdownLanguageServerBasics:
|
||||
@pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
|
||||
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").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
|
||||
# Marksman typically uses kind 15 (String) for markdown headings
|
||||
heading_names = [symbol["name"] for symbol in all_symbols]
|
||||
|
||||
# Should detect headings from README.md
|
||||
assert "Test Repository" in heading_names or len(all_symbols) > 0, "Should find at least one heading"
|
||||
|
||||
# Verify that markdown headings are remapped from String to Namespace
|
||||
for symbol in all_symbols:
|
||||
assert (
|
||||
symbol["kind"] == SymbolKind.Namespace
|
||||
), f"Heading '{symbol['name']}' should have kind Namespace, got {SymbolKind(symbol['kind']).name}"
|
||||
|
||||
@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."""
|
||||
@@ -54,7 +58,6 @@ class TestMarkdownLanguageServerBasics:
|
||||
@pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
|
||||
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").get_all_symbols_and_roots()
|
||||
|
||||
# Should have found some symbols
|
||||
@@ -63,3 +66,30 @@ class TestMarkdownLanguageServerBasics:
|
||||
# Note: Not all markdown LSPs provide body information for symbols
|
||||
# This test is more lenient and just verifies the API works
|
||||
assert all_symbols is not None, "Should return symbols even if body extraction is limited"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
|
||||
def test_markdown_headings_not_low_level(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that markdown headings are not classified as low-level symbols.
|
||||
|
||||
Verifies the fix for the issue where Marksman's SymbolKind.String (15)
|
||||
caused all headings to be filtered out of get_symbols_overview.
|
||||
"""
|
||||
all_symbols, _root_symbols = language_server.request_document_symbols("README.md").get_all_symbols_and_roots()
|
||||
assert len(all_symbols) > 0, "Should find headings in README.md"
|
||||
|
||||
for symbol in all_symbols:
|
||||
ls_symbol = LanguageServerSymbol(symbol)
|
||||
assert (
|
||||
not ls_symbol.is_low_level()
|
||||
), f"Heading '{symbol['name']}' should not be low-level (kind={SymbolKind(symbol['kind']).name})"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
|
||||
def test_markdown_nested_headings_remapped(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that nested headings (h1-h5) are all remapped from String to Namespace."""
|
||||
all_symbols, _root_symbols = language_server.request_document_symbols("api.md").get_all_symbols_and_roots()
|
||||
|
||||
# api.md has deeply nested headings (h1 through h5)
|
||||
assert len(all_symbols) > 5, "api.md should have many headings"
|
||||
|
||||
for symbol in all_symbols:
|
||||
assert symbol["kind"] == SymbolKind.Namespace, f"Nested heading '{symbol['name']}' should be remapped to Namespace"
|
||||
Reference in new issue
Block a user