Project: Make SerenaConfig compulsory, simplifying LanguageServerManager creation

This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2026-03-01 00:50:24 +01:00
1 parent 59f344cc00
commit 1af824c025
8 files changed
+41 -68

No files matched your search

+1 -15
View File
@@ -799,21 +799,7 @@ class SerenaAgent:
"""
Starts/resets the language server manager for the current project
"""
tool_timeout = self.serena_config.tool_timeout
if tool_timeout is None or tool_timeout < 0:
ls_timeout = None
else:
if tool_timeout < 10:
raise ValueError(f"Tool timeout must be at least 10 seconds, but is {tool_timeout} seconds")
ls_timeout = tool_timeout - 5 # the LS timeout is for a single call, it should be smaller than the tool timeout
# instantiate and start the necessary language servers
self.get_active_project_or_raise().create_language_server_manager(
log_level=self.serena_config.log_level,
ls_timeout=ls_timeout,
trace_lsp_communication=self.serena_config.trace_lsp_communication,
ls_specific_settings=self.serena_config.ls_specific_settings,
)
self.get_active_project_or_raise().create_language_server_manager()
def add_language(self, language: Language) -> None:
"""
+1 -3
View File
@@ -640,9 +640,7 @@ class ProjectCommands(AutoRegisteringGroup):
serena_config = SerenaConfig.from_config_file()
proj = registered_project.get_project_instance(serena_config=serena_config)
click.echo(f"Indexing symbols in {proj} …")
ls_mgr = proj.create_language_server_manager(
log_level=lvl, ls_timeout=timeout, ls_specific_settings=serena_config.ls_specific_settings
)
ls_mgr = proj.create_language_server_manager()
try:
log_file = os.path.join(proj.project_root, ".serena", "logs", "indexing.txt")
+1 -1
View File
@@ -488,7 +488,7 @@ class RegisteredProject(ToStringMixin):
"""
return self.project_root.samefile(Path(path).resolve())
def get_project_instance(self, serena_config: "SerenaConfig | None") -> "Project":
def get_project_instance(self, serena_config: "SerenaConfig") -> "Project":
"""
Returns the project instance for this registered project, loading it if necessary.
"""
+18 -19
View File
@@ -10,7 +10,6 @@ from sensai.util.logging import LogTime
from sensai.util.string import ToStringMixin
from serena.config.serena_config import (
DEFAULT_TOOL_TIMEOUT,
ProjectConfig,
get_serena_managed_in_project_dir,
)
@@ -117,13 +116,16 @@ class MemoriesManager:
class Project(ToStringMixin):
def __init__(
self,
*,
project_root: str,
project_config: ProjectConfig,
serena_config: "SerenaConfig",
is_newly_created: bool = False,
serena_config: "SerenaConfig | None" = None,
):
assert serena_config is not None
self.project_root = project_root
self.project_config = project_config
self._serena_config = serena_config
self.memories_manager = MemoriesManager(project_root)
self.language_server_manager: LanguageServerManager | None = None
self._is_newly_created = is_newly_created
@@ -137,7 +139,6 @@ class Project(ToStringMixin):
f.write(f"/{SolidLanguageServer.CACHE_FOLDER_NAME}\n")
# prepare ignore spec asynchronously, ensuring immediate project activation.
self._serena_config = serena_config
self.__ignored_patterns: list[str]
self.__ignore_spec: pathspec.PathSpec
self._ignore_spec_available = threading.Event()
@@ -147,7 +148,7 @@ class Project(ToStringMixin):
with LogTime(f"Gathering ignore spec for project {self.project_config.project_name}", logger=log):
# gather ignored paths from the global configuration, project configuration, and gitignore files
global_ignored_paths = self._serena_config.ignored_paths if self._serena_config else []
global_ignored_paths = self._serena_config.ignored_paths
ignored_patterns = list(global_ignored_paths) + list(self.project_config.ignored_paths)
if len(global_ignored_paths) > 0:
log.info(f"Using {len(global_ignored_paths)} ignored paths from the global configuration.")
@@ -189,7 +190,7 @@ class Project(ToStringMixin):
def load(
cls,
project_root: str | Path,
serena_config: "SerenaConfig | None",
serena_config: "SerenaConfig",
autogenerate: bool = True,
) -> "Project":
project_root = Path(project_root).resolve()
@@ -464,23 +465,21 @@ class Project(ToStringMixin):
source_file_path=relative_file_path,
)
def create_language_server_manager(
self,
log_level: int = logging.INFO,
ls_timeout: float | None = DEFAULT_TOOL_TIMEOUT - 5,
trace_lsp_communication: bool = False,
ls_specific_settings: dict[Language, Any] | None = None,
) -> LanguageServerManager:
def create_language_server_manager(self) -> LanguageServerManager:
"""
Creates the language server manager for the project, starting one language server per configured programming language.
:param log_level: the log level for the language server
:param ls_timeout: the timeout for the language server
:param trace_lsp_communication: whether to trace LSP communication
:param ls_specific_settings: optional LS specific configuration of the language server,
see docstrings in the inits of subclasses of SolidLanguageServer to see what values may be passed.
:return: the language server manager, which is also stored in the project instance
"""
# determine timeout to use for LS calls
tool_timeout = self._serena_config.tool_timeout
if tool_timeout is None or tool_timeout < 0:
ls_timeout = None
else:
if tool_timeout < 10:
raise ValueError(f"Tool timeout must be at least 10 seconds, but is {tool_timeout} seconds")
ls_timeout = tool_timeout - 5 # the LS timeout is for a single call, it should be smaller than the tool timeout
# if there is an existing instance, stop its language servers first
if self.language_server_manager is not None:
log.info("Stopping existing language server manager ...")
@@ -493,8 +492,8 @@ class Project(ToStringMixin):
encoding=self.project_config.encoding,
ignored_patterns=self._ignored_patterns,
ls_timeout=ls_timeout,
ls_specific_settings=ls_specific_settings,
trace_lsp_communication=trace_lsp_communication,
ls_specific_settings=self._serena_config.ls_specific_settings,
trace_lsp_communication=self._serena_config.trace_lsp_communication,
)
self.language_server_manager = LanguageServerManager.from_languages(self.project_config.languages, factory)
return self.language_server_manager
+3 -2
View File
@@ -10,7 +10,7 @@ from typing import Any
import pytest
from sensai.util.logging import configure
from serena.config.serena_config import SerenaPaths
from serena.config.serena_config import SerenaConfig, SerenaPaths
from serena.constants import SERENA_MANAGED_DIR_NAME
from serena.project import Project
from serena.util.file_system import GitignoreParser
@@ -117,7 +117,8 @@ def start_default_ls_context(language: Language) -> Iterator[SolidLanguageServer
def _create_default_project(language: Language) -> Project:
repo_path = str(get_repo_path(language))
return Project.load(repo_path, serena_config=None)
serena_config = SerenaConfig()
return Project.load(repo_path, serena_config=serena_config)
@pytest.fixture(scope="session")
@@ -20,9 +20,7 @@ def _create_test_project(
ignored_paths=project_ignored_paths or [],
ignore_all_files_in_gitignore=False,
)
serena_config: SerenaConfig | None = None
if global_ignored_paths:
serena_config = SerenaConfig(gui_log_window=False, web_dashboard=False, ignored_paths=global_ignored_paths)
serena_config = SerenaConfig(gui_log_window=False, web_dashboard=False, ignored_paths=global_ignored_paths)
return Project(
project_root=str(project_root),
project_config=config,
@@ -84,21 +82,6 @@ class TestGlobalIgnoredPaths:
# Non-ignored files still accessible
assert not project.is_ignored_path(str(self.project_path / "node_modules" / "pkg" / "index.js"))
def test_default_global_ignored_paths_backward_compatible(self) -> None:
"""Project created without global_ignored_paths parameter works as before."""
config = ProjectConfig(
project_name="test_project",
languages=[Language.PYTHON],
ignored_paths=["build"],
ignore_all_files_in_gitignore=False,
)
project = Project(
project_root=str(self.project_path),
project_config=config,
)
assert project.is_ignored_path(str(self.project_path / "build" / "output.js"))
assert not project.is_ignored_path(str(self.project_path / "node_modules" / "pkg" / "index.js"))
def test_duplicate_patterns_across_global_and_project(self) -> None:
"""Duplicate patterns across global and project do not cause errors."""
project = _create_test_project(
@@ -161,7 +144,8 @@ class TestRegisteredProjectGlobalIgnoredPaths:
project_root=str(self.project_path),
project_config=config,
)
project = registered.get_project_instance(serena_config=None)
serena_config = SerenaConfig(gui_log_window=False, web_dashboard=False, ignored_paths=[])
project = registered.get_project_instance(serena_config=serena_config)
assert not project.is_ignored_path(str(self.project_path / "node_modules" / "pkg.js"))
def test_from_project_root_passes_global_ignored_paths(self) -> None:
+10 -6
View File
@@ -191,6 +191,12 @@ def _make_config_with_project(
global_backend: LanguageBackend = LanguageBackend.LSP,
) -> tuple[SerenaConfig, str]:
"""Create a SerenaConfig with a single registered project and return (config, project_name)."""
config = SerenaConfig(
gui_log_window=False,
web_dashboard=False,
log_level=logging.ERROR,
language_backend=global_backend,
)
project = Project(
project_root=str(Path(__file__).parent.parent / "resources" / "repos" / "python" / "test_repo"),
project_config=ProjectConfig(
@@ -198,12 +204,7 @@ def _make_config_with_project(
languages=[Language.PYTHON],
language_backend=language_backend,
),
)
config = SerenaConfig(
gui_log_window=False,
web_dashboard=False,
log_level=logging.ERROR,
language_backend=global_backend,
serena_config=config,
)
config.projects = [RegisteredProject.from_project_instance(project)]
return config, project_name
@@ -259,6 +260,7 @@ class TestEffectiveLanguageBackend:
languages=[Language.PYTHON],
language_backend=LanguageBackend.JETBRAINS,
),
serena_config=config,
)
config.projects.append(RegisteredProject.from_project_instance(jb_project))
@@ -281,6 +283,7 @@ class TestEffectiveLanguageBackend:
languages=[Language.PYTHON],
language_backend=LanguageBackend.LSP,
),
serena_config=config,
)
config.projects.append(RegisteredProject.from_project_instance(lsp_project2))
@@ -303,6 +306,7 @@ class TestEffectiveLanguageBackend:
languages=[Language.PYTHON],
language_backend=None,
),
serena_config=config,
)
config.projects.append(RegisteredProject.from_project_instance(proj2))
+4 -3
View File
@@ -21,7 +21,8 @@ from test.solidlsp import clojure as clj
@pytest.fixture
def serena_config():
"""Create an in-memory configuration for tests with test repositories pre-registered."""
config = SerenaConfig(gui_log_window=False, web_dashboard=False, log_level=logging.ERROR)
# Create test projects for all supported languages
test_projects = []
for language in [
@@ -47,16 +48,16 @@ def serena_config():
project_name=project_name,
languages=[language],
ignored_paths=[],
excluded_tools=set(),
excluded_tools=[],
read_only=False,
ignore_all_files_in_gitignore=True,
initial_prompt="",
encoding="utf-8",
),
serena_config=config,
)
test_projects.append(RegisteredProject.from_project_instance(project))
config = SerenaConfig(gui_log_window=False, web_dashboard=False, log_level=logging.ERROR)
config.projects = test_projects
return config