Skip to content
Merged
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
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,7 @@ tree-sitter-jsdoc = "0.23"
tree-sitter-json = "0.24"
tree-sitter-kotlin-codanna = "0.3.9"
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
tree-sitter-php = "0.24.2"
tree-sitter-python = "0.25"
tree-sitter-regex = "0.24"
tree-sitter-ruby = "0.23"
Expand Down
10 changes: 10 additions & 0 deletions RECAPS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ Running log of completed work sessions, newest first. Each entry summarizes a co

## 2026-05-16

### PHP language support (final of 4 bake-in PRs in this series)
- Added PHP as a built-in language: tree-sitter grammar registered, `.php` / `.phtml` files recognized, syntax highlighting + outline + indents + injections + runnables + debugger queries on, and `intelephense` driven for completion / hover / go-to-def — found on `$PATH` first, otherwise `npm install`ed into the language container dir. Brings the built-in count to 17 (assuming Swift/Kotlin/Java land first).
- Grammar: canonical `tree-sitter-php = "0.24.2"` (MIT). Same dev-dep-only `tree-sitter ^0.25` constraint as `tree-sitter-java` — resolves cleanly against workspace `tree-sitter 0.26`. Exposes `LANGUAGE_PHP` (mixed-mode PHP + inline HTML) and `LANGUAGE_PHP_ONLY` (pure PHP); registered the mixed-mode variant since `.php` files traditionally interleave HTML.
- Asset files: pulled from `zed-extensions/php` (MIT) into `crates/grammars/src/php/`. 11 query files: `brackets.scm`, `config.toml`, `debugger.scm`, `embedding.scm`, `highlights.scm`, `indents.scm`, `injections.scm`, `outline.scm`, `overrides.scm`, `runnables.scm`, `textobjects.scm`. **Zero patches needed** — like Java, the upstream queries are written against the same canonical grammar we registered.
- LSP adapter: new `crates/languages/src/php.rs` (~150 lines). Modeled directly on `typescript.rs` (single npm package version of the dual-package TS pattern). `check_if_user_installed` calls `delegate.which("intelephense")`; otherwise the adapter `npm_install_packages("intelephense", latest)` into `<container>/node_modules/` and invokes `node <container>/node_modules/intelephense/lib/intelephense.js --stdio`. **License note in adapter doc-comment**: intelephense is proprietary (not OSS); we don't redistribute it, `npm install` fetches it from npmjs.com at runtime. Users who prefer OSS alternatives can override via the `language_servers` setting (phpactor, phpls).
- Wired into `crates/languages/src/lib.rs::init`: new `mod php;`, `Arc::new(php::PhpLspAdapter::new(node.clone()))` (no `fs` dep needed, unlike typescript — single-package adapter doesn't do yarn-sdk detection), and a `LanguageInfo { name: "php", adapters: vec![php_lsp_adapter], manifest_name: Some("composer.json".into()), .. }` entry between `markdown-inline` and `python` (alphabetical). All four upstream-shaped edits tagged `// PaddleBoard:`.
- Verified: `cargo check -p grammars --features load-grammars` clean (9.8s, fresh `tree-sitter-php` compile); `cargo check -p paddleboard` clean (6.2s incremental); release build (17.97s) clean. Grammar load: zero query-loader errors in `PaddleBoard.log` after launch. NOT verified end-to-end: user didn't open `php-smoketest.php` during this session, so the `npm install` + intelephense spawn path wasn't exercised. Structurally identical to `typescript.rs` which is known to work; first reviewer can verify by opening any `.php` file (npm install + spawn should take ~10s).
- Preserved: every existing language adapter; the `crates/grammars/` alphabetical ordering convention; `typescript.rs`'s npm pattern unchanged (just simpler-mirrored, not refactored).
- Follow-ups: (1) live end-to-end smoke test pre-merge. (2) **OSS alternative adapter as an option** — `phpactor` is composer-distributed and a viable replacement for users avoiding intelephense's license. (3) Tailwind LSP cross-registration — the existing `tailwind_languages` list in `lib.rs::init` already includes "PHP" as a candidate language for the tailwind LSP, so once this PR lands, tailwind autocompletion in `.php` files Just Works without extra wiring. (4) Composer/PHPUnit context provider for test running. **Bake-in series complete after this PR**: Swift (#28) → Kotlin (#29) → Java (#30) → PHP. Count goes from 13 built-in languages to 17.

### Java language support (PR 3 of 4 in the bake-in series)
- Added Java as a built-in language: tree-sitter grammar registered, `.java` files recognized, syntax highlighting + outline + indents + folds + runnables on, and `jdtls` (Eclipse JDT Language Server) wired for completion / hover / go-to-def when present on `$PATH`. Brings the built-in count to 16 (assuming Swift + Kotlin land first).
- Grammar: canonical `tree-sitter-java = "0.23.5"` (MIT). Initially looked like a version-conflict candidate — its dep manifest pins `tree-sitter ^0.24` — but that's a **dev-dependency only** (for its own tests). Runtime deps are `tree-sitter-language ^0.1` (provides `LanguageFn`) and `cc ^1.1` (builds parser.c), which resolve cleanly against workspace's `tree-sitter 0.26`. No alternative crate needed (unlike Kotlin where we needed the codanna fork). Rationale captured in a multi-line comment on the `native_grammars()` entry so the next reader doesn't repeat the "this won't resolve" diagnostic.
Expand Down
2 changes: 2 additions & 0 deletions crates/grammars/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ tree-sitter-jsdoc = { workspace = true, optional = true }
tree-sitter-json = { workspace = true, optional = true }
tree-sitter-kotlin-codanna = { workspace = true, optional = true }
tree-sitter-md = { workspace = true, optional = true }
tree-sitter-php = { workspace = true, optional = true }
tree-sitter-python = { workspace = true, optional = true }
tree-sitter-regex = { workspace = true, optional = true }
tree-sitter-rust = { workspace = true, optional = true }
Expand All @@ -56,6 +57,7 @@ load-grammars = [
"tree-sitter-json",
"tree-sitter-kotlin-codanna",
"tree-sitter-md",
"tree-sitter-php",
"tree-sitter-python",
"tree-sitter-regex",
"tree-sitter-rust",
Expand Down
8 changes: 8 additions & 0 deletions crates/grammars/src/grammars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ pub fn native_grammars() -> Vec<(&'static str, tree_sitter::Language)> {
("kotlin", tree_sitter_kotlin_codanna::language()),
("markdown", tree_sitter_md::LANGUAGE.into()),
("markdown-inline", tree_sitter_md::INLINE_LANGUAGE.into()),
// PaddleBoard: PHP via canonical tree-sitter/tree-sitter-php (MIT).
// Like tree-sitter-java, its `tree-sitter ^0.25` dep is dev-only;
// runtime uses `tree-sitter-language ^0.1` which resolves cleanly
// against workspace `tree-sitter 0.26`. Uses `LANGUAGE_PHP` (the
// mixed-mode `.php` grammar) — there's also a `LANGUAGE_PHP_ONLY`
// for pure-PHP files but the standard `.php` registration uses
// the mixed-mode grammar that handles inline HTML.
("php", tree_sitter_php::LANGUAGE_PHP.into()),
("python", tree_sitter_python::LANGUAGE.into()),
("regex", tree_sitter_regex::LANGUAGE.into()),
("rust", tree_sitter_rust::LANGUAGE.into()),
Expand Down
4 changes: 4 additions & 0 deletions crates/grammars/src/php/brackets.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
("{" @open "}" @close)
("(" @open ")" @close)
("[" @open "]" @close)
("\"" @open "\"" @close)
22 changes: 22 additions & 0 deletions crates/grammars/src/php/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name = "PHP"
grammar = "php"
path_suffixes = ["php", "phtml"]
first_line_pattern = '^#!.*php'
line_comments = ["// ", "# "]
block_comment = { start = "/*", end = "*/", prefix = "* ", tab_size = 1 }
documentation_comment = { start = "/**", end = "*/", prefix = "* ", tab_size = 1 }
autoclose_before = ";:.,=}])>"
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["string"] },
{ start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] },
]
collapsed_placeholder = "/* ... */"
scope_opt_in_language_servers = ["tailwindcss-language-server"]
prettier_parser_name = "php"
prettier_plugins = ["@prettier/plugin-php"]
completion_query_characters = ["$"]
word_characters = ["$"]
28 changes: 28 additions & 0 deletions crates/grammars/src/php/debugger.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
; e.g. `$age = 25` matches `$age`
(expression_statement
(assignment_expression
left: (variable_name) @debug-variable
)
)

; e.g. `++$age` matches `$age`
(expression_statement
(update_expression
argument: (variable_name) @debug-variable
)
)

; e.g. `if ($age > 18)` matches `$age`
(binary_expression
left: (variable_name) @debug-variable
)

; e.g. `if (18 < $age)` matches `$age`
(binary_expression
right: (variable_name) @debug-variable
)

; e.g. `__construct(int $age)` matches `$age`
(simple_parameter
name: (variable_name) @debug-variable
)
36 changes: 36 additions & 0 deletions crates/grammars/src/php/embedding.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
(
(comment)* @context
.
[
(function_definition
"function" @name
name: (_) @name
body: (_
"{" @keep
"}" @keep) @collapse
)

(trait_declaration
"trait" @name
name: (_) @name)

(method_declaration
"function" @name
name: (_) @name
body: (_
"{" @keep
"}" @keep) @collapse
)

(interface_declaration
"interface" @name
name: (_) @name
)

(enum_declaration
"enum" @name
name: (_) @name
)

] @item
)
Loading