Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
Status of the `main` branch. Changes prior to the next official version change will appear here.

* General:
- Add cross-package reference support via `additional_workspace_folders` project setting (currently implemented for TypeScript).
Configure additional workspace folder paths in `project.yml` to enable `find_referencing_symbols` and `request_references`
to discover symbols across package boundaries in monorepos. The base `SolidLanguageServer` class provides the workspace
folder resolution and activation framework; other language servers can enable support by overriding
`_find_representative_source_file()`. #750
- Support `serena --version` CLI command for displaying the current version #1347
- Fix: Check for ignored path ignored `.git` folder only at the top level, not in every subdirectory (`Project._is_ignored_relative_path`) #1350
- `GetSymbolsOverviewTool`: ignored paths were not respected in LSP variant (fix in `SolidLanguageServer`)
Expand Down
28 changes: 28 additions & 0 deletions docs/02-usage/040_workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ The file allows you to configure ...
* the encoding used in source files
* ignore rules
* write access
* [additional workspace folders](additional-workspace-folders) for cross-package reference support in monorepos
* an initial prompt that shall be passed to the LLM whenever the project is activated
* the name by which you want to refer to the project (relevant when telling the LLM to dynamically activate the project)
* the set of tools and modes to use by default
Expand All @@ -65,6 +66,33 @@ You can specify local overrides for the settings in a `project.local.yml` file i
(which, by default, is ignored by git).
Any keys defined therein will override the respective key in `project.yml`.

(additional-workspace-folders)=
#### Additional Workspace Folders (Cross-Package References)

In monorepos or multi-package setups, Serena's language server normally only sees symbols within the
project root. To enable cross-package references (e.g. `find_referencing_symbols` discovering usages
in sibling packages), configure `additional_workspace_folders` in your `project.yml`:

```yaml
additional_workspace_folders:
- ../shared-lib
- ../api-client
- /absolute/path/to/another-package
```

Paths can be absolute or relative to the project root. Each folder is registered as an LSP workspace
folder, and the language server will discover symbols and references across all listed packages.

**Currently supported for:** TypeScript. Other language servers will raise an error if this setting
is used with them. Support for additional languages can be added by implementing the
`_find_representative_source_file()` method in the respective language server class.

:::{note}
Each additional workspace folder adds startup time, as the language server needs to index the
additional projects. For large monorepos, consider listing only the packages you actively need
cross-references for.
:::

(indexing)=
### Indexing

Expand Down
4 changes: 4 additions & 0 deletions docs/02-usage/050_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,10 @@ Supported settings:
| `typescript_language_server_version` | `5.1.3` | Override the bundled `typescript-language-server` npm package version Serena installs when `ls_path` is not set. |
| `npm_registry` | `null` | Override the npm registry Serena uses for the managed install. |

TypeScript supports [additional workspace folders](additional-workspace-folders) for cross-package
reference discovery in monorepos. Configure `additional_workspace_folders` in `project.yml` (not
under `ls_specific_settings`) to enable this feature.

#### TypeScript via `vtsls`

The actual configuration key for vtsls is `typescript_vts`, not `vts`.
Expand Down
3 changes: 3 additions & 0 deletions src/serena/config/serena_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ class ProjectConfig(SharedConfig):
project_name: str
languages: list[Language]
ignored_paths: list[str] = field(default_factory=list)
additional_workspace_folders: list[str] = field(default_factory=list)
read_only: bool = False
ignore_all_files_in_gitignore: bool = True
initial_prompt: str = ""
Expand Down Expand Up @@ -470,11 +471,13 @@ def _from_dict(cls, data: dict[str, Any], local_override_keys: list[str]) -> Sel
fixed_tools = data["fixed_tools"] or []
excluded_tools = data["excluded_tools"] or []
included_optional_tools = data["included_optional_tools"] or []
additional_workspace_folders = data.get("additional_workspace_folders") or []

return cls(
project_name=data["project_name"],
languages=languages,
ignored_paths=ignored_paths,
additional_workspace_folders=additional_workspace_folders,
excluded_tools=excluded_tools,
fixed_tools=fixed_tools,
included_optional_tools=included_optional_tools,
Expand Down
3 changes: 3 additions & 0 deletions src/serena/ls_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def __init__(
ignored_patterns: list[str],
ls_timeout: float | None = None,
ls_specific_settings: dict | None = None,
additional_workspace_folders: list[str] | None = None,
trace_lsp_communication: bool = False,
):
self.project_root = project_root
Expand All @@ -35,6 +36,7 @@ def __init__(
self.ignored_patterns = ignored_patterns
self.ls_timeout = ls_timeout
self.ls_specific_settings = ls_specific_settings
self.additional_workspace_folders = additional_workspace_folders or []
self.trace_lsp_communication = trace_lsp_communication

def create_language_server(self, language: Language) -> SolidLanguageServer:
Expand All @@ -54,6 +56,7 @@ def create_language_server(self, language: Language) -> SolidLanguageServer:
solidlsp_dir=SerenaPaths().serena_user_home_dir,
project_data_path=self.project_data_path,
ls_specific_settings=self.ls_specific_settings or {},
additional_workspace_folders=self.additional_workspace_folders,
),
)

Expand Down
1 change: 1 addition & 0 deletions src/serena/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,7 @@ def create_language_server_manager(self) -> LanguageServerManager:
ignored_patterns=self._ignored_patterns,
ls_timeout=ls_timeout,
ls_specific_settings=ls_specific_settings,
additional_workspace_folders=self.project_config.additional_workspace_folders,
trace_lsp_communication=self.serena_config.trace_lsp_communication,
)
self.language_server_manager = LanguageServerManager.from_languages(self.project_config.languages, factory)
Expand Down
11 changes: 11 additions & 0 deletions src/serena/resources/project.template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ ignore_all_files_in_gitignore: true
# No documentation on options means no options are available.
ls_specific_settings: {}

# list of additional workspace folder paths for cross-package reference support (e.g. in monorepos).
# Paths can be absolute or relative to the project root.
# Each folder is registered as an LSP workspace folder, enabling language servers to discover
# symbols and references across package boundaries.
# Currently supported for: TypeScript.
# Example:
# additional_workspace_folders:
# - ../sibling-package
# - ../shared-lib
additional_workspace_folders: []

# list of additional paths to ignore in this project.
# Same syntax as gitignore, so you can use * and **.
# Note: global ignored_paths from serena_config.yml are also applied additively.
Expand Down
46 changes: 40 additions & 6 deletions src/solidlsp/language_servers/typescript_language_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,12 +252,7 @@ def _get_initialize_params(self, repository_absolute_path: str) -> InitializePar
"processId": os.getpid(),
"rootPath": repository_absolute_path,
"rootUri": root_uri,
"workspaceFolders": [
{
"uri": root_uri,
"name": os.path.basename(repository_absolute_path),
}
],
"workspaceFolders": self._build_workspace_folders_param(repository_absolute_path),
}
return cast(InitializeParams, initialize_params)

Expand Down Expand Up @@ -393,6 +388,45 @@ def progress_handler(params: dict) -> None:
self.INDEXING_PROGRESS_TIMEOUT,
)

self._activate_additional_workspaces()

@override
def _find_representative_source_file(self, directory: str) -> str | None:
"""Find a TypeScript file suitable for triggering project loading.

Prefers a file adjacent to tsconfig.json (indicating the project root),
then falls back to the first .ts/.tsx file found.
"""
for root, dirs, files in os.walk(directory):
dirs[:] = [d for d in dirs if not self.is_ignored_dirname(d)]
if "tsconfig.json" in files:
for f in files:
if f.endswith((".ts", ".tsx")) and not f.endswith(".d.ts"):
return os.path.join(root, f)
src_dir = os.path.join(root, "src")
if os.path.isdir(src_dir):
for f in os.listdir(src_dir):
if f.endswith((".ts", ".tsx")) and not f.endswith(".d.ts"):
return os.path.join(src_dir, f)

for root, dirs, files in os.walk(directory):
dirs[:] = [d for d in dirs if not self.is_ignored_dirname(d)]
for f in files:
if f.endswith((".ts", ".tsx")) and not f.endswith(".d.ts"):
return os.path.join(root, f)
return None

@override
def _signal_expect_indexing(self) -> None:
self.expect_indexing()

@override
def _wait_for_additional_workspace_indexing(self) -> None:
if self.wait_for_indexing(timeout=self.INDEXING_PROGRESS_TIMEOUT):
log.info("Additional workspace indexing complete")
else:
log.warning("Additional workspace indexing did not complete within timeout; proceeding anyway")

@override
def _get_wait_time_for_cross_file_referencing(self) -> float:
return 2
Expand Down
Loading
Loading