Add Wolfram Language support via official LSPServer paclet (#1108)

Adds support for Wolfram Language (.wl, .wls files) using the official
WolframResearch LSPServer paclet, which communicates via stdio.
Requires Wolfram Mathematica 13.0+ or Wolfram Engine 12.1+.
The WolframKernel is located via WOLFRAM_PATH, the system PATH, common
install locations, or ls_path in ls_specific_settings.

Includes language server implementation, test repo, test suite
(skipped gracefully when WolframKernel is unavailable), and
documentation updates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-08-09 13:31:30 +02:00
1 parent c687e751af
commit 1b459899ca
12 files changed
+309 -13

No files matched your search

+11 -11
View File
@@ -6,17 +6,17 @@ project_name: "project_name"
# bsl clojure cpp cpp_ccls crystal
# csharp csharp_omnisharp cue dart deno
# elixir elm erlang fortran fsharp
# gdscript go groovy haskell haxe
# hlsl html java json julia
# kotlin latex lean4 lua luau
# markdown matlab msl nextflow nix
# ocaml pascal perl php php_phpactor
# php_phpantom powershell python python_basedpyright python_jedi
# python_pyrefly python_ty qml r rego
# ruby ruby_solargraph rust scala scss
# solidity svelte swift systemverilog terraform
# toml typescript typescript_vts vue yaml
# zig
# gdscript gleam go groovy haskell
# haxe hlsl html java json
# julia kotlin latex lean4 lua
# luau markdown matlab msl nextflow
# nix ocaml pascal perl php
# php_phpactor php_phpantom powershell python python_basedpyright
# python_jedi python_pyrefly python_ty qml r
# rego ruby ruby_solargraph rust scala
# scss solidity svelte swift systemverilog
# terraform toml typescript typescript_vts vue
# wolfram yaml zig
# (This list may be outdated; generated with scripts/print_language_list.py;
# For the current list, see values of the LanguageServerId enum here:
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py)
@@ -0,0 +1,183 @@
"""Wolfram Language server integration using the official WolframResearch LSPServer paclet."""
import glob
import logging
import os
import platform
import shutil
from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
WOLFRAM_PATH_ENV_VAR = "WOLFRAM_PATH"
class WolframLanguageServer(SolidLanguageServer):
"""
Wolfram Language server using the official WolframResearch LSPServer paclet
(https://github.com/WolframResearch/LSPServer), which is bundled with Wolfram
installations and communicates via stdio.
Requires Wolfram Mathematica 13.0+ or Wolfram Engine 12.1+.
The WolframKernel executable is discovered via the ``ls_path`` entry in
``ls_specific_settings.wolfram``, the ``WOLFRAM_PATH`` environment variable
(pointing to the executable or the installation directory), the system PATH,
or common installation locations.
"""
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
super().__init__(config, repository_root_path, None, "wolfram", solidlsp_settings)
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:
"""
Discover the WolframKernel executable.
:return: path to the WolframKernel executable
:raises FileNotFoundError: if WolframKernel cannot be found
"""
return _find_wolfram_kernel()
def _create_launch_command(self, core_path: str) -> list[str]:
return [core_path, "-noprompt", "-noinit", "-run", 'Needs["LSPServer`"];LSPServer`StartServer[]']
def _create_base_initialize_params(self) -> dict:
return {
"capabilities": {
"workspace": {"workspaceFolders": True},
"textDocument": {
"definition": {"dynamicRegistration": True},
"references": {"dynamicRegistration": True},
"documentSymbol": {
"dynamicRegistration": True,
"hierarchicalDocumentSymbolSupport": True,
},
"hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
"formatting": {"dynamicRegistration": True},
"publishDiagnostics": {"relatedInformation": True},
},
},
}
def _start_server(self) -> None:
def do_nothing(params: dict) -> None:
return
def window_log_message(msg: dict) -> None:
log.info(f"Wolfram LSP: window/logMessage: {msg}")
self.server.on_request("client/registerCapability", do_nothing)
self.server.on_notification("window/logMessage", window_log_message)
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
log.info("Starting Wolfram LSPServer process")
self.server.start()
initialize_params = self._create_initialize_params()
init_response = self.server.send.initialize(initialize_params)
log.info(f"Wolfram LSP capabilities: {list(init_response.get('capabilities', {}).keys())}")
self.server.notify.initialized({})
log.info("Wolfram LSPServer initialized and ready.")
def _find_wolfram_kernel() -> str:
"""Find the WolframKernel executable via WOLFRAM_PATH, the system PATH, or common install locations."""
# 1. WOLFRAM_PATH environment variable (executable or installation directory)
env_path = os.environ.get(WOLFRAM_PATH_ENV_VAR)
if env_path:
if os.path.isfile(env_path) and os.access(env_path, os.X_OK):
log.info(f"Using WolframKernel from {WOLFRAM_PATH_ENV_VAR}: {env_path}")
return env_path
kernel_in_dir = _find_kernel_in_install_dir(env_path)
if kernel_in_dir:
log.info(f"Using WolframKernel from {WOLFRAM_PATH_ENV_VAR} directory: {kernel_in_dir}")
return kernel_in_dir
# 2. System PATH
kernel_path = shutil.which("WolframKernel")
if kernel_path:
log.info(f"Using WolframKernel from PATH: {kernel_path}")
return kernel_path
# 3. Common installation locations
system = platform.system()
search_locations: list[str] = []
if system == "Darwin":
search_locations = [
"/Applications/Mathematica.app/Contents/MacOS/WolframKernel",
"/Applications/Wolfram.app/Contents/MacOS/WolframKernel",
"/Applications/Wolfram Engine.app/Contents/MacOS/WolframKernel",
]
for pattern in [
"/Applications/Mathematica*.app/Contents/MacOS/WolframKernel",
"/Applications/Wolfram*.app/Contents/MacOS/WolframKernel",
]:
search_locations.extend(sorted(glob.glob(pattern), reverse=True))
elif system == "Linux":
search_locations = [
"/usr/local/bin/WolframKernel",
"/usr/bin/WolframKernel",
]
for pattern in [
"/usr/local/Wolfram/Mathematica/*/Executables/WolframKernel",
"/usr/local/Wolfram/WolframEngine/*/Executables/WolframKernel",
"/opt/Wolfram/Mathematica/*/Executables/WolframKernel",
]:
search_locations.extend(sorted(glob.glob(pattern), reverse=True))
elif system == "Windows":
for pattern in [
"C:\\Program Files\\Wolfram Research\\Mathematica\\*\\WolframKernel.exe",
"C:\\Program Files\\Wolfram Research\\Wolfram Engine\\*\\WolframKernel.exe",
]:
search_locations.extend(sorted(glob.glob(pattern), reverse=True))
for location in search_locations:
if os.path.isfile(location) and os.access(location, os.X_OK):
log.info(f"Found WolframKernel at: {location}")
return location
raise FileNotFoundError(
"WolframKernel not found. Please either:\n"
f"1. Set the {WOLFRAM_PATH_ENV_VAR} environment variable to your Wolfram installation\n"
"2. Add WolframKernel to your system PATH\n"
"3. Configure ls_path in ls_specific_settings.wolfram\n"
"4. Install Wolfram Mathematica (13.0+) or Wolfram Engine (12.1+) from https://www.wolfram.com/"
)
def _find_kernel_in_install_dir(install_dir: str) -> str | None:
"""Try to locate WolframKernel within a Wolfram installation directory."""
system = platform.system()
if system == "Darwin":
candidates = [
os.path.join(install_dir, "Contents", "MacOS", "WolframKernel"),
os.path.join(install_dir, "MacOS", "WolframKernel"),
]
elif system == "Windows":
candidates = [
os.path.join(install_dir, "WolframKernel.exe"),
]
else:
candidates = [
os.path.join(install_dir, "Executables", "WolframKernel"),
os.path.join(install_dir, "WolframKernel"),
]
for candidate in candidates:
if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
return candidate
return None
+11
View File
@@ -196,6 +196,11 @@ class LanguageServerId(str, Enum):
it reports no symbols for them, so they are not treated as source files here).
Automatically downloads the language server JAR; requires Java 17+ (JAVA_HOME or 'java' on PATH).
"""
WOLFRAM = "wolfram"
"""Wolfram Language server using the official WolframResearch LSPServer paclet.
Requires Wolfram Mathematica 13.0+ or Wolfram Engine 12.1+.
Set WOLFRAM_PATH environment variable or configure ls_path in ls_specific_settings.
"""
# Experimental or deprecated Language Servers
TYPESCRIPT_VTS = "typescript_vts"
"""Use the typescript language server through the natively bundled vscode extension via https://github.com/yioneko/vtsls"""
@@ -598,6 +603,8 @@ class LanguageServerId(str, Enum):
# only scripts: the language server does have a service for .config files, but it provides
# no symbols for them, so treating them as source files would only pollute the symbol index
return FilenameMatcher(".nf")
case self.WOLFRAM:
return FilenameMatcher(".wl", ".wls")
case self.HTML:
return FilenameMatcher(".html", ".htm")
case self.SCSS:
@@ -901,6 +908,10 @@ class LanguageServerId(str, Enum):
from solidlsp.language_servers.nextflow_language_server import NextflowLanguageServer
return NextflowLanguageServer
case self.WOLFRAM:
from solidlsp.language_servers.wolfram_language_server import WolframLanguageServer
return WolframLanguageServer
case self.HTML:
from solidlsp.language_servers.vscode_html_language_server import VsCodeHtmlLanguageServer