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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ Status of the `main` branch. Changes prior to the next official version change w
- Make tool call errors surface explicitly as errors at the MCP protocol level

* Language Servers:
- Perl: expose `file_filter` and `ignore_dirs` via `ls_specific_settings["perl"]`, so projects with
Comment thread
opcode81 marked this conversation as resolved.
non-standard extensions (e.g. `.cgi`, `.psgi`) can make those files visible to Perl::LanguageServer.
Configured extensions are also synced into the Perl source-file matcher (now a cached singleton),
keeping `find_symbol` / symbol indexing consistent with the LS; the matcher is reset on every
language server activation so one project's reconfiguration does not leak into the next.
`FilenameMatcher` gains `add_extensions` / `reset` methods. Defaults are unchanged. #1449
- C/C++ (clangd): improve support and documentation for Unreal Engine 5 projects.
- `typescript_vts`: Add `initialization_options` setting in `ls_specific_settings.typescript_vts`.
Enables Yarn PnP setups with `typescript.tsdk` pointing at the Yarn-generated SDK.
Expand Down
28 changes: 28 additions & 0 deletions docs/02-usage/050_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,34 @@ Notes:
- Use the FPC compiler driver (`fpc`/`fpc.exe`), not backend compilers like `ppc386.exe`.
- These settings are passed as environment variables to the pasls process.

#### Perl

Serena uses [Perl::LanguageServer](https://metacpan.org/pod/Perl::LanguageServer) for Perl support. Install Perl and the server with `cpanm Perl::LanguageServer`; Linux and macOS only (the server does not run on Windows).

Perl::LanguageServer only indexes files whose extension is in its `perl.fileFilter` and skips directories listed in `perl.ignoreDirs`. Both are exposed below so projects with non-standard extensions (e.g. `.cgi` / `.psgi` web handlers) can make those files visible (#1449).

**Configuration:**

Configure the language server via `ls_specific_settings.perl` in `serena_config.yml`:

| Setting | Default | Description |
| -------------- | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `file_filter` | `[".pm", ".pl", ".t"]` | File extensions (with leading dot) that Perl::LanguageServer should index, e.g. `[".pm", ".pl", ".t", ".cgi"]`. |
| `ignore_dirs` | `[".git", ".svn", "blib", "local", ".carton", "vendor", "_build", "cover_db"]` | Directory names Perl::LanguageServer should skip when indexing. |

Example configuration:

```yaml
ls_specific_settings:
perl:
file_filter: [".pm", ".pl", ".t", ".cgi", ".psgi"]
ignore_dirs: [".git", "blib", "local", "vendor", "cover_db"]
```

Notes:
- Extensions added via `file_filter` are also synced into Serena's Perl source-file matcher, so `find_symbol` and symbol indexing treat the same files as the language server. Defaults are unchanged when these keys are omitted.
- The matcher is reset on every language server activation, so one project's `file_filter` does not leak into another.

#### PHP (`Intelephense`)

Serena uses Intelephense for the `php` language key.
Expand Down
58 changes: 49 additions & 9 deletions src/solidlsp/language_servers/perl_language_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,34 @@
from overrides import override

from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_config import Language, LanguageServerConfig
from solidlsp.ls_utils import PlatformId, PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import DidChangeConfigurationParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings

log = logging.getLogger(__name__)

# Defaults handed to Perl::LanguageServer via its `perl.fileFilter` / `perl.ignoreDirs` settings.
# Exposed for override via `ls_specific_settings["perl"]`. Extensions added via `file_filter` are
# also synced into Language.PERL.get_source_fn_matcher() (a cached singleton) so Serena's symbol
# index and the language server agree on which files are Perl sources (#1449).
_DEFAULT_FILE_FILTER: list[str] = [".pm", ".pl", ".t"]
_DEFAULT_IGNORE_DIRS: list[str] = [".git", ".svn", "blib", "local", ".carton", "vendor", "_build", "cover_db"]


class PerlLanguageServer(SolidLanguageServer):
"""
Provides Perl specific instantiation of the LanguageServer class using Perl::LanguageServer.
"""

# Keep in sync with Language.PERL.get_source_fn_matcher() in solidlsp/ls_config.py:
# extensions missing here are invisible to Perl::LanguageServer's project index.
_FILE_FILTER: list = [".pm", ".pl", ".t"]
You can pass the following entries in ``ls_specific_settings["perl"]``:
- file_filter: List of file extensions (with leading dot) that Perl::LanguageServer should
index, e.g. ``[".pm", ".pl", ".t", ".cgi"]``. Defaults to ``[".pm", ".pl", ".t"]``.
The same extensions are added to Serena's Perl source-file matcher, so ``find_symbol``
and symbol indexing treat them consistently (see #1449).
- ignore_dirs: Directory names Perl::LanguageServer should skip when indexing. Defaults to
``[".git", ".svn", "blib", "local", ".carton", "vendor", "_build", "cover_db"]``.
"""

@staticmethod
def _get_perl_version() -> str | None:
Expand Down Expand Up @@ -101,6 +112,31 @@ def _setup_runtime_dependencies(cls) -> str:

return "perl -MPerl::LanguageServer -e 'Perl::LanguageServer::run'"

@staticmethod
def _resolve_filter_settings(solidlsp_settings: SolidLSPSettings) -> tuple[list[str], list[str]]:
"""Resolve the ``fileFilter`` / ``ignoreDirs`` to hand to Perl::LanguageServer.

Reads optional overrides from ``ls_specific_settings["perl"]`` (keys ``file_filter`` and
``ignore_dirs``); falls back to the defaults otherwise. Extracted as a pure function so the
configuration plumbing can be unit-tested without starting the language server.
"""
perl_settings = solidlsp_settings.get_ls_specific_settings(Language.PERL)
file_filter = perl_settings.get("file_filter", list(_DEFAULT_FILE_FILTER))
ignore_dirs = perl_settings.get("ignore_dirs", list(_DEFAULT_IGNORE_DIRS))
return file_filter, ignore_dirs

@staticmethod
def _sync_source_fn_matcher(file_filter: list[str]) -> None:
"""Keep Serena's Perl source-file matcher in sync with the LS ``fileFilter``.

``Language.PERL.get_source_fn_matcher()`` is a ``@cache``d per-language singleton, so adding
the configured extensions here propagates to every consumer of the matcher — symbol index
traversal (``project.is_ignored_path``), the LS ignore check (``ls.is_ignored_path``), and
language composition detection. Without this, ``find_symbol`` would not surface symbols in
files whose extensions were added to ``file_filter`` (#1449).
"""
Language.PERL.get_source_fn_matcher().add_extensions(*file_filter)

def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
# Setup runtime dependencies before initializing
perl_ls_cmd = self._setup_runtime_dependencies()
Expand All @@ -109,6 +145,10 @@ def __init__(self, config: LanguageServerConfig, repository_root_path: str, soli
config, repository_root_path, ProcessLaunchInfo(cmd=perl_ls_cmd, cwd=repository_root_path), "perl", solidlsp_settings
)
self.request_id = 0
self._file_filter, self._ignore_dirs = self._resolve_filter_settings(solidlsp_settings)
# Sync Serena's source-file matcher with the configured extensions so find_symbol and the
# language server agree on which files are Perl sources (see #1449).
self._sync_source_fn_matcher(self._file_filter)

def _create_base_initialize_params(self) -> dict:
"""
Expand Down Expand Up @@ -153,8 +193,8 @@ def workspace_configuration_handler(params: Any) -> Any:

perl_config = {
"perlInc": [self.repository_root_path, "."],
"fileFilter": self._FILE_FILTER,
"ignoreDirs": [".git", ".svn", "blib", "local", ".carton", "vendor", "_build", "cover_db"],
"fileFilter": self._file_filter,
"ignoreDirs": self._ignore_dirs,
}

return [perl_config]
Expand Down Expand Up @@ -189,8 +229,8 @@ def workspace_configuration_handler(params: Any) -> Any:
"settings": {
"perl": {
"perlInc": [self.repository_root_path, "."],
"fileFilter": self._FILE_FILTER,
"ignoreDirs": [".git", ".svn", "blib", "local", ".carton", "vendor", "_build", "cover_db"],
"fileFilter": self._file_filter,
"ignoreDirs": self._ignore_dirs,
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/solidlsp/ls.py
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,11 @@ def __init__(
"""
identifies the language server (not to be confused with the language id passed to the language server)
"""
# The source filename matcher is a @cache'd per-language singleton. A previous project may
# have extended it (e.g. Perl's file_filter adding .cgi); reset it here so every activation
# starts from the language's default extensions, then language-server subclasses re-apply
# their own settings during the rest of __init__.
self.language.get_source_fn_matcher().reset()
self._published_diagnostics: dict[str, list[ls_types.Diagnostic]] = {}
self._published_diagnostics_generation_by_uri: dict[str, int] = {}
self._published_diagnostics_generation = 0
Expand Down
32 changes: 32 additions & 0 deletions src/solidlsp/ls_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,38 @@ def __init__(self, *file_extensions: str, case_sensitive: bool = True) -> None:
"""
self._file_extensions = list(set(file_extensions)) if case_sensitive else list(set(ext.lower() for ext in file_extensions))
self._case_sensitive = case_sensitive
# Snapshot of the initial configuration, used by ``reset``. Relevant for matchers that are
# per-language singletons (``Language.get_source_fn_matcher`` is ``@cache``d): extensions added
# via ``add_extensions`` for one project must not leak into the next, so the singleton is reset
# to this snapshot at every language server initialisation.
self._initial_file_extensions = list(self._file_extensions)

def reset(self) -> None:
"""
Restore the matcher to its initial set of extensions (as provided at construction).

Undoes any extensions added via :meth:`add_extensions`. Intended for the per-language
singleton matchers, which are reset at the start of every language server initialisation so
that a previous project's reconfiguration does not leak into a newly activated one.
"""
self._file_extensions = list(self._initial_file_extensions)

def add_extensions(self, *file_extensions: str) -> None:
"""
Add further file extensions to this matcher (idempotent).

This is intended for matchers that are per-language singletons, i.e. those returned by
:meth:`Language.get_source_fn_matcher` (which is ``@cache``d): extensions that a user
configures for a language server (e.g. ``.cgi`` for Perl) can be added here so that every
consumer of the matcher — symbol index traversal, ignore checks, language composition —
treats the same set of files as sources, staying in sync with the language server.

:param file_extensions: the additional file extensions, e.g. ``.cgi``
"""
for ext in file_extensions:
norm = ext if self._case_sensitive else ext.lower()
if norm not in self._file_extensions:
self._file_extensions.append(norm)

def is_relevant_filename(self, fn: str) -> bool:
if not self._case_sensitive:
Expand Down
Empty file added test/solidlsp/perl/__init__.py
Empty file.
Loading
Loading