Add HLSL and related languages support through shader-language-server (#1093)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
DevonandClaude Opus 4.6 authored and GitHub committed 2026-03-04 19:28:50 +01:00
1 parent dadf3f6a18
commit dbd7bf7596
12 files changed
+603 -3

No files matched your search

+1 -1
View File
@@ -82,7 +82,7 @@ that implement the language server protocol (LSP).
The underlying language servers are typically open-source projects (like Serena) or at least freely available for use.
With Serena's LSP library, we provide **support for over 30 programming languages**, including
AL, Bash, C#, C/C++, Clojure, Dart, Elixir, Elm, Erlang, Fortran, Go, Groovy (partial support), Haskell, Java, Javascript, Julia, Kotlin, Lua, Markdown, MATLAB, Nix, Perl, PHP, PowerShell, Python, R, Ruby, Rust, Scala, Swift, TOML, TypeScript, YAML, and Zig.
AL, Bash, C#, C/C++, Clojure, Dart, Elixir, Elm, Erlang, Fortran, GLSL, Go, Groovy (partial support), Haskell, HLSL, Java, Javascript, Julia, Kotlin, Lua, Markdown, MATLAB, Nix, Perl, PHP, PowerShell, Python, R, Ruby, Rust, Scala, Swift, TOML, TypeScript, WGSL, YAML, and Zig.
> [!IMPORTANT]
> Some language servers require additional dependencies to be installed; see the [Language Support](https://oraios.github.io/serena/01-about/020_programming-languages.html) page for details.
+5 -1
View File
@@ -52,8 +52,12 @@ Some languages require additional installations or setup steps, as noted.
(requires installation of `gopls`)
* **Groovy**
(requires local groovy-language-server.jar setup via `GROOVY_LS_JAR_PATH` or configuration)
* **Haskell**
* **Haskell**
(automatically locates HLS via ghcup, stack, or system PATH; supports Stack and Cabal projects)
* **HLSL / GLSL / WGSL**
(uses [shader-language-server](https://github.com/antaalt/shader-sense) (language `hlsl`); automatically downloaded;
on macOS, requires Rust toolchain for building from source;
note: reference search is not supported by this language server)
* **Java**
* **JavaScript**
* **Julia**
+2 -1
View File
@@ -326,11 +326,12 @@ markers = [
"toml: language server running for TOML",
"matlab: language server running for MATLAB (requires MATLAB R2021b+)",
"systemverilog: language server running for SystemVerilog (uses verible-verilog-ls)",
"hlsl: language server running for HLSL shaders (uses shader-language-server)",
]
[tool.codespell]
# Ref: https://github.com/codespell-project/codespell#using-a-config-file
skip = '.git*,*.svg,*.lock,*.min.*'
check-hidden = true
# ignore-regex = ''
ignore-regex = '\.\w+'
ignore-words-list = 'paket'
@@ -0,0 +1,219 @@
"""
Shader language server using shader-language-server (antaalt/shader-sense).
Supports HLSL, GLSL, and WGSL shader file formats.
"""
import logging
import os
import pathlib
import shutil
from typing import Any, cast
from overrides import override
from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.settings import SolidLSPSettings
from .common import RuntimeDependency, RuntimeDependencyCollection
log = logging.getLogger(__name__)
# GitHub release version to download when not installed locally
_DEFAULT_VERSION = "1.3.0"
_GITHUB_RELEASE_BASE = "https://github.com/antaalt/shader-sense/releases/download"
class HlslLanguageServer(SolidLanguageServer):
"""
Shader language server using shader-language-server.
Supports .hlsl, .hlsli, .fx, .fxh, .cginc, .compute, .shader, .glsl, .vert, .frag, .geom, .tesc, .tese, .comp, .wgsl files.
"""
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings) -> None:
super().__init__(config, repository_root_path, None, "hlsl", solidlsp_settings)
@override
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
class DependencyProvider(LanguageServerDependencyProviderSinglePath):
def _get_or_install_core_dependency(self) -> str:
# 1. Check PATH for system-installed binary
system_binary = shutil.which("shader-language-server")
if system_binary:
log.info(f"Using system-installed shader-language-server at {system_binary}")
return system_binary
# 2. Try to download pre-built binary from GitHub releases
version = self._custom_settings.get("version", _DEFAULT_VERSION)
tag = f"v{version}"
base_url = f"{_GITHUB_RELEASE_BASE}/{tag}"
# macOS has no pre-built binaries; build from source via cargo install
cargo_install_cmd = f"cargo install shader_language_server --version {version} --root ."
deps = RuntimeDependencyCollection(
[
RuntimeDependency(
id="shader-language-server",
description="shader-language-server for Windows (x64)",
url=f"{base_url}/shader-language-server-x86_64-pc-windows-msvc.zip",
platform_id="win-x64",
archive_type="zip",
binary_name="shader-language-server.exe",
),
RuntimeDependency(
id="shader-language-server",
description="shader-language-server for Linux (x64)",
url=f"{base_url}/shader-language-server-x86_64-unknown-linux-gnu.zip",
platform_id="linux-x64",
archive_type="zip",
binary_name="shader-language-server",
),
RuntimeDependency(
id="shader-language-server",
description="shader-language-server for Windows (ARM64)",
url=f"{base_url}/shader-language-server-aarch64-pc-windows-msvc.zip",
platform_id="win-arm64",
archive_type="zip",
binary_name="shader-language-server.exe",
),
RuntimeDependency(
id="shader-language-server",
description="shader-language-server for macOS (x64) - built from source",
command=cargo_install_cmd,
platform_id="osx-x64",
binary_name="bin/shader-language-server",
),
RuntimeDependency(
id="shader-language-server",
description="shader-language-server for macOS (ARM64) - built from source",
command=cargo_install_cmd,
platform_id="osx-arm64",
binary_name="bin/shader-language-server",
),
]
)
try:
dep = deps.get_single_dep_for_current_platform()
except RuntimeError:
dep = None
if dep is None:
raise FileNotFoundError(
"shader-language-server is not installed and no auto-install is available for your platform.\n"
"Please install it using one of the following methods:\n"
" cargo: cargo install shader_language_server\n"
" GitHub: Download from https://github.com/antaalt/shader-sense/releases\n"
"On macOS, install the Rust toolchain (https://rustup.rs) and Serena will build from source automatically.\n"
"See https://github.com/antaalt/shader-sense for more details."
)
install_dir = os.path.join(self._ls_resources_dir, "shader-language-server")
executable_path = deps.binary_path(install_dir)
if not os.path.exists(executable_path):
log.info(f"shader-language-server not found. Downloading from {dep.url}")
_ = deps.install(install_dir)
if not os.path.exists(executable_path):
raise FileNotFoundError(f"shader-language-server not found at {executable_path}")
os.chmod(executable_path, 0o755)
return executable_path
def _create_launch_command(self, core_path: str) -> list[str]:
return [core_path, "--stdio"]
@staticmethod
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
root_uri = pathlib.Path(repository_absolute_path).as_uri()
initialize_params = {
"processId": os.getpid(),
"rootPath": repository_absolute_path,
"rootUri": root_uri,
"locale": "en",
"capabilities": {
"textDocument": {
"synchronization": {"didSave": True, "dynamicRegistration": True},
"completion": {
"dynamicRegistration": True,
"completionItem": {"snippetSupport": True},
},
"definition": {"dynamicRegistration": True},
"hover": {
"dynamicRegistration": True,
"contentFormat": ["markdown", "plaintext"],
},
"signatureHelp": {
"dynamicRegistration": True,
"signatureInformation": {
"parameterInformation": {"labelOffsetSupport": True},
},
},
"documentSymbol": {
"dynamicRegistration": True,
"hierarchicalDocumentSymbolSupport": True,
"symbolKind": {"valueSet": list(range(1, 27))},
},
"formatting": {"dynamicRegistration": True},
"publishDiagnostics": {"relatedInformation": True},
},
"workspace": {
"workspaceFolders": True,
"didChangeConfiguration": {"dynamicRegistration": True},
"configuration": True,
},
},
"workspaceFolders": [{"uri": root_uri, "name": os.path.basename(repository_absolute_path)}],
}
return cast(InitializeParams, initialize_params)
@override
def _start_server(self) -> None:
def do_nothing(params: Any) -> None:
return
def on_log_message(params: Any) -> None:
message = params.get("message", "") if isinstance(params, dict) else str(params)
log.info(f"shader-language-server: {message}")
def on_configuration_request(params: Any) -> list[dict]:
"""Respond to workspace/configuration requests.
shader-language-server requests config with section 'shader-validator'.
Return empty config to use defaults.
"""
items = params.get("items", []) if isinstance(params, dict) else []
return [{}] * len(items)
self.server.on_request("client/registerCapability", do_nothing)
self.server.on_request("workspace/configuration", on_configuration_request)
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.server.on_notification("window/logMessage", on_log_message)
log.info("Starting shader-language-server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
log.info("Sending initialize request")
init_response = self.server.send.initialize(initialize_params)
capabilities = init_response.get("capabilities", {})
log.info(f"Initialize response capabilities: {list(capabilities.keys())}")
assert "textDocumentSync" in capabilities, "shader-language-server must support textDocumentSync"
if "documentSymbolProvider" not in capabilities:
log.warning("shader-language-server does not advertise documentSymbolProvider")
if "definitionProvider" not in capabilities:
log.warning("shader-language-server does not advertise definitionProvider")
self.server.notify.initialized({})
@override
def is_ignored_dirname(self, dirname: str) -> bool:
"""Ignore Unity-specific directories that contain no user-authored shaders."""
return super().is_ignored_dirname(dirname) or dirname in {"Library", "Temp", "Logs", "obj", "Packages"}
+27
View File
@@ -105,6 +105,11 @@ class Language(str, Enum):
"""TOML language server using Taplo.
Supports TOML validation, formatting, and schema support.
"""
HLSL = "hlsl"
"""Shader language server using shader-language-server (antaalt/shader-sense).
Supports .hlsl, .hlsli, .fx, .fxh, .cginc, .compute, .shader, .glsl, .vert, .frag, .geom, .tesc, .tese, .comp, .wgsl files.
Automatically downloads shader-language-server binary.
"""
SYSTEMVERILOG = "systemverilog"
"""SystemVerilog language server using verible-verilog-ls.
Supports .sv, .svh, .v, .vh files.
@@ -251,6 +256,24 @@ class Language(str, Enum):
return FilenameMatcher("*.groovy", "*.gvy")
case self.MATLAB:
return FilenameMatcher("*.m", "*.mlx", "*.mlapp")
case self.HLSL:
return FilenameMatcher(
"*.hlsl",
"*.hlsli",
"*.fx",
"*.fxh",
"*.cginc",
"*.compute",
"*.shader",
"*.glsl",
"*.vert",
"*.frag",
"*.geom",
"*.tesc",
"*.tese",
"*.comp",
"*.wgsl",
)
case self.SYSTEMVERILOG:
return FilenameMatcher("*.sv", "*.svh", "*.v", "*.vh")
case _:
@@ -434,6 +457,10 @@ class Language(str, Enum):
from solidlsp.language_servers.matlab_language_server import MatlabLanguageServer
return MatlabLanguageServer
case self.HLSL:
from solidlsp.language_servers.hlsl_language_server import HlslLanguageServer
return HlslLanguageServer
case self.SYSTEMVERILOG:
from solidlsp.language_servers.systemverilog_server import SystemVerilogLanguageServer
@@ -0,0 +1,29 @@
#ifndef COMMON_HLSL
#define COMMON_HLSL
struct VertexInput
{
float3 position : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct VertexOutput
{
float4 clipPos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float2 uv : TEXCOORD1;
};
float3 SafeNormalize(float3 v)
{
float len = length(v);
return len > 0.0001 ? v / len : float3(0, 0, 0);
}
float Remap(float value, float fromMin, float fromMax, float toMin, float toMax)
{
return toMin + (value - fromMin) * (toMax - toMin) / (fromMax - fromMin);
}
#endif // COMMON_HLSL
@@ -0,0 +1,26 @@
#include "common.hlsl"
RWTexture2D<float4> OutputTexture : register(u0);
Texture2D<float4> InputTexture : register(t0);
cbuffer ComputeParams : register(b0)
{
uint2 TextureSize;
float BlurRadius;
float _Pad;
};
[numthreads(8, 8, 1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
if (id.x >= TextureSize.x || id.y >= TextureSize.y)
return;
float4 color = InputTexture[id.xy];
float3 remapped = float3(
Remap(color.r, 0.0, 1.0, 0.2, 0.8),
Remap(color.g, 0.0, 1.0, 0.2, 0.8),
Remap(color.b, 0.0, 1.0, 0.2, 0.8)
);
OutputTexture[id.xy] = float4(remapped, color.a);
}
@@ -0,0 +1,37 @@
#ifndef LIGHTING_HLSL
#define LIGHTING_HLSL
#include "common.hlsl"
cbuffer LightingConstants : register(b0)
{
float4x4 ViewProjection;
float3 LightDirection;
float LightIntensity;
float3 AmbientColor;
float _Padding;
};
float3 CalculateDiffuse(float3 normal, float3 lightDir, float3 albedo)
{
float ndotl = max(dot(normal, -lightDir), 0.0);
return albedo * ndotl;
}
float3 CalculateSpecular(float3 normal, float3 lightDir, float3 viewDir, float shininess)
{
float3 halfVec = SafeNormalize(-lightDir + viewDir);
float ndoth = max(dot(normal, halfVec), 0.0);
return pow(ndoth, shininess);
}
VertexOutput TransformVertex(VertexInput input)
{
VertexOutput output;
output.clipPos = mul(ViewProjection, float4(input.position, 1.0));
output.worldNormal = input.normal;
output.uv = input.uv;
return output;
}
#endif // LIGHTING_HLSL
@@ -0,0 +1,33 @@
#ifndef TERRAIN_SDF_HLSL
#define TERRAIN_SDF_HLSL
#include "../common.hlsl"
struct SDFBrickData
{
float3 center;
float halfExtent;
int resolution;
float maxDistance;
};
float3 WorldOffset;
float SampleSDF(float3 worldPos, SDFBrickData brick)
{
float3 localPos = worldPos - brick.center;
float dist = length(localPos) - brick.halfExtent;
return dist;
}
float3 CalculateGradient(float3 worldPos, SDFBrickData brick)
{
float eps = 0.01;
float3 gradient;
gradient.x = SampleSDF(worldPos + float3(eps, 0, 0), brick) - SampleSDF(worldPos - float3(eps, 0, 0), brick);
gradient.y = SampleSDF(worldPos + float3(0, eps, 0), brick) - SampleSDF(worldPos - float3(0, eps, 0), brick);
gradient.z = SampleSDF(worldPos + float3(0, 0, eps), brick) - SampleSDF(worldPos - float3(0, 0, eps), brick);
return SafeNormalize(gradient);
}
#endif // TERRAIN_SDF_HLSL
+1
View File
@@ -0,0 +1 @@
+160
View File
@@ -0,0 +1,160 @@
"""
Basic tests for HLSL language server integration (shader-language-server).
This module tests Language.HLSL using shader-language-server from antaalt/shader-sense.
Tests are skipped if the language server is not available.
"""
from typing import Any
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_exceptions import SolidLSPException
from solidlsp.ls_utils import SymbolUtils
def _find_symbol_by_name(language_server: SolidLanguageServer, file_path: str, name: str) -> dict[str, Any] | None:
"""Find a top-level symbol by name in a file's document symbols."""
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
return next((s for s in symbols[0] if s.get("name") == name), None)
# ── Symbol Discovery ─────────────────────────────────────────────
@pytest.mark.hlsl
class TestHlslSymbols:
"""Tests for document symbol extraction."""
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
def test_find_struct(self, language_server: SolidLanguageServer) -> None:
"""VertexInput struct should appear in common.hlsl symbols."""
symbol = _find_symbol_by_name(language_server, "common.hlsl", "VertexInput")
assert symbol is not None, "Expected 'VertexInput' struct in document symbols"
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
def test_find_function(self, language_server: SolidLanguageServer) -> None:
"""SafeNormalize function should appear in common.hlsl."""
symbol = _find_symbol_by_name(language_server, "common.hlsl", "SafeNormalize")
assert symbol is not None, "Expected 'SafeNormalize' function in document symbols"
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
def test_find_cbuffer_members(self, language_server: SolidLanguageServer) -> None:
"""Cbuffer members should appear as variables in compute_test.hlsl.
Note: shader-language-server reports cbuffer members as individual
variables (kind 13), not the cbuffer name itself as a symbol.
"""
symbol = _find_symbol_by_name(language_server, "compute_test.hlsl", "TextureSize")
assert symbol is not None, "Expected 'TextureSize' cbuffer member in document symbols"
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
def test_find_compute_kernel(self, language_server: SolidLanguageServer) -> None:
"""CSMain kernel should appear in compute_test.hlsl."""
symbol = _find_symbol_by_name(language_server, "compute_test.hlsl", "CSMain")
assert symbol is not None, "Expected 'CSMain' compute kernel in document symbols"
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
def test_full_symbol_tree(self, language_server: SolidLanguageServer) -> None:
"""Full symbol tree should contain symbols from multiple files."""
symbols = language_server.request_full_symbol_tree()
assert SymbolUtils.symbol_tree_contains_name(symbols, "VertexInput"), "VertexInput not in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "CalculateDiffuse"), "CalculateDiffuse not in symbol tree"
# ── Go-to-Definition ─────────────────────────────────────────────
@pytest.mark.hlsl
class TestHlslDefinition:
"""Tests for go-to-definition capability."""
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
def test_goto_definition_cross_file(self, language_server: SolidLanguageServer) -> None:
"""Navigating to SafeNormalize call in lighting.hlsl should resolve to common.hlsl.
lighting.hlsl line 22 (0-indexed): " float3 halfVec = SafeNormalize(-lightDir + viewDir);"
SafeNormalize starts at column 21.
"""
definitions = language_server.request_definition("lighting.hlsl", 22, 21)
assert len(definitions) >= 1, f"Expected at least 1 definition, got {len(definitions)}"
def_paths = [d.get("relativePath", d.get("uri", "")) for d in definitions]
assert any("common.hlsl" in p for p in def_paths), f"Expected definition in common.hlsl, got: {def_paths}"
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
def test_goto_definition_cross_file_remap(self, language_server: SolidLanguageServer) -> None:
"""Navigating to Remap call in compute_test.hlsl should resolve to common.hlsl.
compute_test.hlsl line 20 (0-indexed): " Remap(color.r, 0.0, 1.0, 0.2, 0.8),"
Remap starts at column 8.
"""
definitions = language_server.request_definition("compute_test.hlsl", 20, 8)
assert len(definitions) >= 1, f"Expected at least 1 definition, got {len(definitions)}"
def_paths = [d.get("relativePath", d.get("uri", "")) for d in definitions]
assert any("common.hlsl" in p for p in def_paths), f"Expected definition in common.hlsl, got: {def_paths}"
# ── References ────────────────────────────────────────────────────
@pytest.mark.hlsl
class TestHlslReferences:
"""Tests for find-references capability.
shader-language-server does not advertise referencesProvider, so
request_references is expected to return an empty list.
"""
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
def test_references_not_supported(self, language_server: SolidLanguageServer) -> None:
"""References request should raise because shader-language-server does not support it.
common.hlsl line 17 (0-indexed): "float3 SafeNormalize(float3 v)"
SafeNormalize starts at column 7.
"""
with pytest.raises(SolidLSPException, match="Method not found"):
language_server.request_references("common.hlsl", 17, 7)
# ── Hover ─────────────────────────────────────────────────────────
def _extract_hover_text(hover_info: dict[str, Any]) -> str:
"""Extract the text content from an LSP hover response."""
contents = hover_info["contents"]
if isinstance(contents, dict):
return contents.get("value", "")
elif isinstance(contents, str):
return contents
return str(contents)
@pytest.mark.hlsl
class TestHlslHover:
"""Tests for hover information."""
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
def test_hover_on_function(self, language_server: SolidLanguageServer) -> None:
"""Hovering over SafeNormalize definition should return info.
common.hlsl line 17 (0-indexed): "float3 SafeNormalize(float3 v)"
SafeNormalize starts at column 7.
"""
hover_info = language_server.request_hover("common.hlsl", 17, 7)
assert hover_info is not None, "Hover should return information for SafeNormalize"
assert "contents" in hover_info, "Hover should have contents"
hover_text = _extract_hover_text(hover_info)
assert len(hover_text) > 0, "Hover text should not be empty"
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
def test_hover_on_struct(self, language_server: SolidLanguageServer) -> None:
"""Hovering over VertexInput should return struct info.
common.hlsl line 3 (0-indexed): "struct VertexInput"
VertexInput starts at column 7.
"""
hover_info = language_server.request_hover("common.hlsl", 3, 7)
assert hover_info is not None, "Hover should return information for VertexInput"
assert "contents" in hover_info, "Hover should have contents"
@@ -0,0 +1,63 @@
"""
Regression tests for HLSL full symbol tree indexing.
These tests verify that request_full_symbol_tree() correctly indexes all files,
including .hlsl includes in subdirectories. This catches bugs where files are
silently dropped during workspace-wide indexing.
"""
from typing import Any
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind
from solidlsp.ls_utils import SymbolUtils
def _collect_file_names(symbols: list[dict[str, Any]]) -> set[str]:
"""Recursively collect the names of all File-kind symbols in the tree."""
names: set[str] = set()
for sym in symbols:
if sym.get("kind") == SymbolKind.File:
names.add(sym["name"])
if "children" in sym:
names.update(_collect_file_names(sym["children"]))
return names
EXPECTED_FILES = {"common", "lighting", "compute_test", "terrain_sdf"}
TERRAIN_SDF_UNIQUE_SYMBOLS = {"SampleSDF", "CalculateGradient", "SDFBrickData"}
@pytest.mark.hlsl
class TestHlslFullIndex:
"""Tests for full symbol tree indexing completeness."""
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
def test_all_files_indexed_in_symbol_tree(self, language_server: SolidLanguageServer) -> None:
"""Every .hlsl file in the test repo must appear as a File symbol in the tree."""
symbols = language_server.request_full_symbol_tree()
file_names = _collect_file_names(symbols)
missing = EXPECTED_FILES - file_names
assert not missing, f"Files missing from full symbol tree: {missing}. Found: {file_names}"
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
def test_subdirectory_file_symbols_present(self, language_server: SolidLanguageServer) -> None:
"""Symbols unique to terrain/terrain_sdf.hlsl must appear in the full tree."""
symbols = language_server.request_full_symbol_tree()
for name in TERRAIN_SDF_UNIQUE_SYMBOLS:
assert SymbolUtils.symbol_tree_contains_name(
symbols, name
), f"Expected '{name}' from terrain/terrain_sdf.hlsl in full symbol tree"
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
def test_include_file_document_symbols_directly(self, language_server: SolidLanguageServer) -> None:
"""request_document_symbols on terrain/terrain_sdf.hlsl should return its symbols."""
doc_symbols = language_server.request_document_symbols("terrain/terrain_sdf.hlsl")
all_symbols = doc_symbols.get_all_symbols_and_roots()
symbol_names = {s.get("name") for s in all_symbols[0]}
for name in TERRAIN_SDF_UNIQUE_SYMBOLS:
assert name in symbol_names, f"Expected '{name}' in document symbols for terrain/terrain_sdf.hlsl, got: {symbol_names}"