A language server for the regex patterns of PHP files: diagnostics, hovers, completions and code actions in any LSP editor.
The server reads PHP source over stdio, finds the regex literals in it — the patterns of preg_* calls, of library calls such as Preg::match() or $this->assertMatchesRegularExpression(), and of the calls to functions whose parameter is marked #[RegexPattern] — and answers in JSON-RPC, so every LSP editor can check patterns while you type.
Documentation: php-regex.com — the LSP guide gets diagnostics into your editor.
- Diagnostics on open and on change: every pattern of the file is parsed, validated and linted, and the findings are published to the editor; a pattern PCRE refuses gets that error alone.
- Hovers in plain English: hold the pointer on a pattern to read what it matches, token by token.
- Completions inside patterns: shorthands after a backslash, Unicode properties after
\p{, POSIX classes after[:, groups after(?, flags after the closing delimiter. - Code actions: one action adds the
uflag a Unicode pattern lacks, one applies the optimizer's rewrite —/[0-9]{1,}/becomes/\d+/— and two lay a long pattern out over lines underxor compact it back to one line. - The calls
regex lintreads: the native ones (preg_*,RegexIterator,filter_var()), the presetsextraction.interopinregex.jsonnames (every one by default; an unknown name is an error, shown, and the server reads as withoutregex.json), the helpersextraction.functionsnames, and a pattern held in a constant, a variable or a property of the same file, diagnosed at its literal;@regex-ignoreworks as in the lint. A pattern assembled from several pieces at the call, or written as a heredoc, is reported byregex lintbut gets no diagnostic in the editor. - Functions marked
#[RegexPattern](or PhpStorm's#[Language('RegExp')]): their calls are checked at that argument, the declarations read at start-up from the workspace's PHP files (underpaths, out ofexcludeinregex.json) and fromvendor/whateverexcludesays (read again when the editor reportsvendor/composer/installed.jsonchanged), and from the open documents as you type. A document invendor/gets no diagnostics. - One target per workspace: patterns are judged for one PHP and PCRE2 pair, resolved from the editor settings,
regex.json,composer.jsonor the running PHP.
composer require --dev php-regex/regex-language-serverRequires PHP 8.1+ with the tokenizer extension. The binary is installed as vendor/bin/regex-lsp; --help prints its options, --version the release it was built from.
Every pattern of the workspace is judged for one target, a PHP and PCRE2 pair, resolved once at initialize and logged where the editor shows the server output (Target: PHP 8.4.26, PCRE2 10.49 (running PHP)).
| Option | Accepts | When absent |
|---|---|---|
initializationOptions.phpVersion |
"8.2", "8.2.4" or 80200 |
phpVersion in regex.json, then composer.json, then the running PHP |
initializationOptions.pcreVersion |
a PCRE2 release, "10.42" for instance |
pcreVersion in regex.json, then the release the target PHP bundles |
regex.json (and regex.dist.json) sits at the root of the first workspace folder. A value the server cannot read is logged as a warning and the next source is used. Changing the target takes a server restart.
A file opens, and a broken pattern is reported where it sits. This notification was captured from a live session:
{"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///demo/Mailer.php","diagnostics":[
{"range":{"start":{"line":2,"character":13},"end":{"line":2,"character":16}},"severity":1,"code":"regex.delimiter.unclosed",
"source":"php-regex","message":"No closing delimiter \"/\" found. You opened with \"/\"; expected closing \"/\". Tip: escape \"/\" inside the pattern (\\/) or use a different delimiter, e.g. #[a-#."}]}}Hovering the pattern /^\w+@\w+\.\w+$/ returns this markdown (abridged):
**Regex Pattern**
```
/^\w+@\w+\.\w+$/
```
**Explanation**
Regex matches
Anchor: the beginning of a line
Character Type: A word character: [a-zA-Z_0-9] (one or more times)
…
Anchor: the end of a line
Code actions are offered on the pattern they fix:
/\p{L}+/without theuflag gets "Add /u flag for Unicode support", rewriting the literal to'/\p{L}+/u'./[0-9]{1,}/gets "Apply regex optimization", rewriting the literal to'/\d+/'; a rewrite is offered only once the automata prove it, asregex lintchecks its suggestions.- A pattern too long for its line gets "Format pattern over lines", which lays it out under
x, its continuation lines indented four spaces past the literal's line; a laid-out pattern gets "Compact pattern to one line". Each is offered only when it changes the literal.
VS Code has no setting that points at an arbitrary language server: a small
extension built on
vscode-languageclient
starts vendor/bin/regex-lsp for PHP files. The
editor guide shows the whole client.
Install LSP4IJ from the JetBrains Marketplace, then Settings → Languages & Frameworks → Language Servers: a server definition named PHPRegex, command vendor/bin/regex-lsp, file mappings *.php.
init.lua with nvim-lspconfig:
local lspconfig = require('lspconfig')
local configs = require('lspconfig.configs')
if not configs.php_regex then
configs.php_regex = {
default_config = {
cmd = { 'vendor/bin/regex-lsp' },
filetypes = { 'php' },
root_dir = lspconfig.util.root_pattern('composer.json', '.git'),
},
}
end
lspconfig.php_regex.setup({})Vim, Emacs, Sublime Text, Helix and Zed are wired the same way; the LSP guide holds each snippet.
- LSP guide — editor configurations, supported methods, troubleshooting.
- Diagnostics reference — the codes the server publishes and how to read them.
- Backward compatibility promise — what stays stable across releases.
If PHPRegex saves you time, consider sponsoring its maintenance.
MIT. See LICENSE.