From f6e942dd00949f007e1471c39575d968fa86bd4b Mon Sep 17 00:00:00 2001 From: arookieofc Date: Sun, 8 Nov 2026 17:36:30 +0800 Subject: [PATCH] feat(languages): start Laravel LSP next to Intelephense in Laravel projects (#2117) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Laravel ships a first-party language server (laravel/lsp, the one behind the official VS Code/Zed/Sublime extensions) that answers hover, definition and completion inside config('…'), route('…') and view('…') calls — places Intelephense returns null. Register it in languages/php.toml after Intelephense and the phpactor fallback (Intelephense keeps hover, rename and symbols for PHP code), gated by require-root-marker on artisan so it only starts in Laravel projects — the same pattern angular-language-server uses for angular.json. Add a ToolSpec so Settings › Language Servers can install it: GitHub releases carry plain per-platform binaries (server-v*--[.exe]), which Method::Release + ArchiveKind::Raw already handle; the versioned asset name lands in bin/ via the usual link/wrapper step. laravel-lsp runs the project's PHP, so needs stays empty (PHP detection is the project interpreter's job). Closes #2100 --- crates/ide-language/src/registry.rs | 21 +++++++++++++++++++++ crates/ide-tools/src/platform.rs | 19 +++++++++++++++++++ crates/ide-tools/src/registry.rs | 29 +++++++++++++++++++++++++++++ languages/php.toml | 11 +++++++++++ 4 files changed, 80 insertions(+) diff --git a/crates/ide-language/src/registry.rs b/crates/ide-language/src/registry.rs index 091eb7f3e..39bc2b092 100644 --- a/crates/ide-language/src/registry.rs +++ b/crates/ide-language/src/registry.rs @@ -692,6 +692,27 @@ mod tests { } } + #[test] + fn laravel_lsp_serves_php_files_only_below_artisan() { + let reg = reg(); + let cfg = reg.get(&LanguageId::new("php")).unwrap(); + let names: Vec<&str> = cfg.language_servers.iter().map(|s| s.name.as_str()).collect(); + // After the fallback: hover, rename and symbols stay with Intelephense. + assert_eq!(names, ["intelephense", "phpactor", "laravel-lsp"]); + let lsp = cfg.language_servers.iter().find(|s| s.name == "laravel-lsp").unwrap(); + assert_eq!(lsp.command.as_str(), "laravel-lsp"); + assert!(lsp.require_root_marker && !lsp.fallback); + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + for dir in ["shop", "lib"] { + std::fs::create_dir_all(root.join(dir)).unwrap(); + } + std::fs::write(root.join("shop/artisan"), "").unwrap(); + let served = |file: &str| crate::marked_root(Some(root), &root.join(file), &lsp.root_markers); + assert_eq!(served("shop/app/Models/Order.php"), Some(root.join("shop"))); + assert_eq!(served("lib/src/Parser.php"), None); + } + #[test] fn ruff_serves_python_files_only_below_its_config() { let reg = reg(); diff --git a/crates/ide-tools/src/platform.rs b/crates/ide-tools/src/platform.rs index d27e89cec..7b202c27d 100644 --- a/crates/ide-tools/src/platform.rs +++ b/crates/ide-tools/src/platform.rs @@ -151,6 +151,25 @@ mod tests { assert_eq!(select_asset(&names, rules, WIN), Some(names[4])); } + #[test] + fn laravel_lsp_assets() { + let names = [ + "server-v0.0.32-arm64-darwin", + "server-v0.0.32-x64-darwin", + "server-v0.0.32-arm64-linux", + "server-v0.0.32-x64-linux", + "server-v0.0.32-x64-win32.exe", + ]; + let rules = crate::registry::find("laravel-lsp").unwrap().asset_rules(); + assert_eq!(select_asset(&names, rules, MAC_ARM), Some(names[0])); + assert_eq!(select_asset(&names, rules, MAC_X64), Some(names[1])); + assert_eq!(select_asset(&names, rules, LINUX_ARM), Some(names[2])); + assert_eq!(select_asset(&names, rules, LINUX_X64), Some(names[3])); + assert_eq!(select_asset(&names, rules, WIN), Some(names[4])); + // The composer-installed helper is not a release asset. + assert_eq!(select_asset(&["laravel-lsp"], rules, LINUX_X64), None); + } + #[test] fn kmp_lsp_assets_include_the_native_jar_indexer() { let names = [ diff --git a/crates/ide-tools/src/registry.rs b/crates/ide-tools/src/registry.rs index 73dbfb181..17c3f619d 100644 --- a/crates/ide-tools/src/registry.rs +++ b/crates/ide-tools/src/registry.rs @@ -396,6 +396,35 @@ pub static TOOLS: &[ToolSpec] = &[ homepage: "https://intelephense.com", needs: &[], }, + ToolSpec { + name: "laravel-lsp", + display: "Laravel LSP", + kind: LanguageServer, + commands: &["laravel-lsp"], + // Plain binaries per platform (`server-v*--[.exe]`). + method: Method::Release { + source: ReleaseSource::GitHub { repo: "laravel/lsp" }, + assets: &[ + rule(Mac, Arm64, "server-v*-arm64-darwin"), + rule(Mac, X64, "server-v*-x64-darwin"), + rule(Linux, Arm64, "server-v*-arm64-linux"), + rule(Linux, X64, "server-v*-x64-linux"), + rule(Windows, X64, "server-v*-x64-win32.exe"), + ], + locate: &[ + "laravel-lsp", + "server-v*-arm64-darwin", + "server-v*-x64-darwin", + "server-v*-arm64-linux", + "server-v*-x64-linux", + "server-v*-x64-win32", + ], + }, + fallback: None, + version: ver("laravel-lsp"), + homepage: "https://github.com/laravel/lsp", + needs: &[], + }, ToolSpec { name: "gopls", display: "gopls", diff --git a/languages/php.toml b/languages/php.toml index e3dcf927d..44ecf04d3 100644 --- a/languages/php.toml +++ b/languages/php.toml @@ -26,6 +26,17 @@ args = ["language-server"] root-markers = ["composer.json", ".phpactor.json"] install-hint = "Install phpactor (https://phpactor.readthedocs.io/en/master/usage/standalone.html)" +# Laravel projects only: hover, definition and completion inside +# config('…'), route('…') and view('…'). Last: Intelephense keeps PHP hover, +# rename and symbols. https://github.com/laravel/lsp +[[language-servers]] +name = "laravel-lsp" +command = "laravel-lsp" +args = ["--stdio"] +root-markers = ["artisan"] +require-root-marker = true +install-hint = "composer global require laravel/lsp" + [debugger] adapter = "php-debug" command = "php-debug-adapter"