| 1 | # LSP: PHP Support & Custom Language Server Extension |
| 2 | |
| 3 | > v0.8.65+ | `codex/lsp-php-custom-servers` |
| 4 | |
| 5 | ## Overview |
| 6 | |
| 7 | This feature adds **PHP** to the built-in LSP language registry and introduces a |
| 8 | `[lsp.custom]` config section so users can register arbitrary LSP servers by |
| 9 | file extension — covering languages absent from the built-in `Language` enum |
| 10 | (Ruby, C#, Swift, Lua, etc.). |
| 11 | |
| 12 | ## Changes |
| 13 | |
| 14 | ### 1. PHP built-in support |
| 15 | |
| 16 | - `Language::Php` variant added to the enum in `crates/tui/src/lsp/registry.rs` |
| 17 | - `.php` files are detected and routed to `intelephense --stdio` by default |
| 18 | - User can override via `[lsp.servers].php` |
| 19 | |
| 20 | ### 2. Custom LSP server extension |
| 21 | |
| 22 | New struct `CustomLspDef` (defined in `crates/tui/src/lsp/mod.rs` and mirrored |
| 23 | in `crates/config/src/lib.rs`): |
| 24 | |
| 25 | ```rust |
| 26 | pub struct CustomLspDef { |
| 27 | pub language_id: String, // LSP languageId for textDocument/didOpen |
| 28 | pub command: String, // executable to spawn |
| 29 | pub args: Vec<String>, // arguments (default empty) |
| 30 | } |
| 31 | ``` |
| 32 | |
| 33 | New config field `LspConfig.custom: HashMap<String, CustomLspDef>` — keyed by |
| 34 | file extension (without dot), e.g. `"rb"`, `"cs"`, `"swift"`. |
| 35 | |
| 36 | In `LspManager::diagnostics_for`, when the built-in registry returns |
| 37 | `Language::Other`, the manager checks the user's custom table before giving up. |
| 38 | Custom servers get their own lazy-spawn transport map and once-per-extension |
| 39 | missing-binary warnings (no log spam). |
| 40 | |
| 41 | ### 3. Transport generalization |
| 42 | |
| 43 | `StdioLspTransport::spawn` now accepts `&str language_id` instead of |
| 44 | `Language`, so both built-in and custom servers share the same transport |
| 45 | implementation. The old `Language` import is removed from `client.rs`. |
| 46 | |
| 47 | ### 4. Polling pipeline extracted |
| 48 | |
| 49 | `poll_diagnostics` is a new private method that both built-in and custom |
| 50 | diagnostics paths share — eliminating duplicated call/wait/filter/sort/truncate |
| 51 | logic. |
| 52 | |
| 53 | ## Configuration |
| 54 | |
| 55 | ### Built-in PHP (enabled by default if `intelephense` is on PATH) |
| 56 | |
| 57 | ```toml |
| 58 | # No config needed — PHP .php files are detected automatically. |
| 59 | # Override the server if desired: |
| 60 | [lsp.servers] |
| 61 | php = ["phpactor", "language-server"] |
| 62 | ``` |
| 63 | |
| 64 | ### Custom language servers |
| 65 | |
| 66 | ```toml |
| 67 | [lsp.custom.rb] |
| 68 | command = "ruby-lsp" |
| 69 | args = ["--stdio"] |
| 70 | language_id = "ruby" |
| 71 | |
| 72 | [lsp.custom.cs] |
| 73 | command = "csharp-ls" |
| 74 | language_id = "csharp" |
| 75 | |
| 76 | [lsp.custom.swift] |
| 77 | command = "sourcekit-lsp" |
| 78 | language_id = "swift" |
| 79 | ``` |
| 80 | |
| 81 | Keys are file extensions (no leading dot). The `args` field defaults to empty. |
| 82 | `language_id` must match what the LSP server expects in `textDocument/didOpen`. |
| 83 | |
| 84 | ## Architecture |
| 85 | |
| 86 | ``` |
| 87 | edit_file / write_file / apply_patch success |
| 88 | │ |
| 89 | ▼ |
| 90 | LspManager.diagnostics_for(file) |
| 91 | │ |
| 92 | ├── custom_for_extension(file) ── found? ──► transport_for_custom(ext, def) |
| 93 | │ │ |
| 94 | ├── detect_language(file) ── Other? ──► return None (skip) |
| 95 | │ |
| 96 | └── transport_for(lang) |
| 97 | │ |
| 98 | ▼ |
| 99 | poll_diagnostics(file, text, transport) |
| 100 | │ |
| 101 | ▼ |
| 102 | DiagnosticBlock → injected into session message stream |
| 103 | ``` |
| 104 | |
| 105 | ## Verification |
| 106 | |
| 107 | ``` |
| 108 | cargo test -p codewhale-tui --bin codewhale-tui lsp:: |
| 109 | # 32 tests passed (3 new: detects_php_extension, language_ids_for_php, |
| 110 | # server_for_php_is_intelephense) |
| 111 | cargo clippy -p codewhale-tui --bin codewhale-tui |
| 112 | # lsp module: zero new warnings |
| 113 | ``` |
| 114 | |
| 115 | ## Files touched |
| 116 | |
| 117 | | File | Change | |
| 118 | |------|--------| |
| 119 | | `crates/tui/src/lsp/registry.rs` | +Php variant, detection, server mapping, tests | |
| 120 | | `crates/tui/src/lsp/mod.rs` | +CustomLspDef, LspConfig.custom, LspManager custom fallback | |
| 121 | | `crates/tui/src/lsp/client.rs` | spawn accepts &str language_id | |
| 122 | | `crates/tui/src/config.rs` | LspConfigToml.custom + into_runtime | |
| 123 | | `crates/config/src/lib.rs` | LspConfigToml.custom + CustomLspDef | |
| 124 | | `config.example.toml` | PHP docs + custom extension examples | |
| 125 |