.serena/memories/adding_new_language_support_guide.md
This guide explains how to add support for a new programming language to Serena.
Adding a new language involves:
Create a new file in src/solidlsp/language_servers/ (e.g., new_language_server.py).
All language servers use the DependencyProvider pattern to handle
To implement a new language server using the DependencyProvider pattern:
None for process_launch_info in super().__init__() - the base class creates it via _create_dependency_provider()_create_dependency_provider() to return an inner DependencyProvider class instance.
In simple cases, it can be instantiated with only two parameters:
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
Base Classes:
LanguageServerDependencyProviderSinglePath - For language servers with a single core dependency (e.g., an executable or JAR file)
ls_path custom setting, allowing users to override the core dependency path (if they have it installed it themselves)_get_or_install_core_dependency() to return the path to the core dependency, downloading/installing it automatically if necessary_create_launch_command(core_path) to build the full command from the core pathTypeScriptLanguageServer, Intelephense, ClojureLSP, ClangdLanguageServer, PyrightServerLanguageServerDependencyProvider - The base class, which can be directly inherited from for complex cases with multiple dependencies or custom setup
create_launch_command() directlyEclipseJDTLS, CSharpLanguageServer, MatlabLanguageServerImplementation Pointers::
create_launch_command_env if the launch command needs environment variables to be set (defaults to {} in the base implementation)You should look at at least one existing implementation of each base class to understand how they work.
Override initialization methods if needed:
def _get_initialize_params(self) -> InitializeParams:
"""Return language-specific initialization parameters."""
return {
"processId": os.getpid(),
"rootUri": PathUtils.path_to_uri(self.repository_root_path),
"capabilities": {
# Language-specific capabilities
}
}
def _start_server(self):
"""Start the language server with custom handlers."""
# Set up notification handlers
self.server.on_notification("window/logMessage", self._handle_log_message)
# Start server and initialize
self.server.start()
init_response = self.server.send.initialize(self._get_initialize_params())
self.server.notify.initialized({})
After _start_server returns, the language server should be fully operational.
If the server requires that one waits for certain notifications or responses before being ready, implement that logic here.
For an example, see EclipseJDTLS._start_server.
In src/solidlsp/ls_config.py, add your language to the Language enum:
class Language(str, Enum):
# Existing languages...
NEW_LANGUAGE = "new_language"
def get_source_fn_matcher(self) -> FilenameMatcher:
match self:
# Existing cases...
case self.NEW_LANGUAGE:
return FilenameMatcher("*.newlang", "*.nl") # File extensions
In src/solidlsp/ls.py, add your language to the create method:
@classmethod
def create(cls, config: LanguageServerConfig, repository_root_path: str) -> "SolidLanguageServer":
match config.code_language:
# Existing cases...
case Language.NEW_LANGUAGE:
from solidlsp.language_servers.new_language_server import NewLanguageServer
return NewLanguageServer(config, repository_root_path)
Create a minimal project in test/resources/repos/new_language/test_repo/:
test/resources/repos/new_language/test_repo/
├── main.newlang # Main source file
├── lib/
│ └── helper.newlang # Additional source for testing
├── project.toml # Project configuration (if applicable)
└── .gitignore # Ignore build artifacts
Create meaningful source files that demonstrate:
Example main.newlang:
import lib.helper
class Calculator {
func add(a: Int, b: Int) -> Int {
return a + b
}
func subtract(a: Int, b: Int) -> Int {
return helper.subtract(a, b) // Reference to imported function
}
}
class Program {
func main() {
let calc = Calculator()
let result = calc.add(5, 3) // Reference to add method
print(result)
}
}
Testing the language server implementation is of crucial importance, and the tests will form the main part of the review process. Make sure that the tests are up to the standard of Serena to make the review go smoother.
General rules for tests:
Create test/solidlsp/new_language/test_new_language_basic.py.
Have a look at the structure of existing tests, for example, in test/solidlsp/php/test_php_basic.py
You should at least test:
Have a look at test/solidlsp/php/test_php_basic.py as an example for what should be tested.
Don't forget to add a new language marker to pytest.ini.
Consider adding new cases to the parametrized tests in test_serena_agent.py for the new language.
Update: