mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 11:41:04 +00:00
feat(lsp): support for loading lspServers configurations from extensions
This commit is contained in:
parent
0bff045d3d
commit
7ec79e6806
8 changed files with 278 additions and 211 deletions
|
|
@ -38,7 +38,7 @@ You need to have the language server for your programming language installed:
|
|||
|
||||
### .lsp.json File
|
||||
|
||||
You can configure language servers using a `.lsp.json` file in your project root. This follows the [Claude Code plugin LSP configuration format](https://code.claude.com/docs/en/plugins-reference#lsp-servers).
|
||||
You can configure language servers using a `.lsp.json` file in your project root. This uses the language-keyed format described in the [Claude Code plugin LSP configuration reference](https://code.claude.com/docs/en/plugins-reference#lsp-servers).
|
||||
|
||||
**Basic format:**
|
||||
|
||||
|
|
@ -57,28 +57,6 @@ You can configure language servers using a `.lsp.json` file in your project root
|
|||
}
|
||||
```
|
||||
|
||||
**Extended format with `languageServers` wrapper:**
|
||||
|
||||
```json
|
||||
{
|
||||
"languageServers": {
|
||||
"typescript-language-server": {
|
||||
"languages": [
|
||||
"typescript",
|
||||
"javascript",
|
||||
"typescriptreact",
|
||||
"javascriptreact"
|
||||
],
|
||||
"command": "typescript-language-server",
|
||||
"args": ["--stdio"],
|
||||
"transport": "stdio",
|
||||
"initializationOptions": {},
|
||||
"settings": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration Options
|
||||
|
||||
#### Required Fields
|
||||
|
|
@ -346,7 +324,7 @@ Or check the LSP debugging guide at `packages/cli/LSP_DEBUGGING_GUIDE.md`.
|
|||
|
||||
## Claude Code Compatibility
|
||||
|
||||
Qwen Code supports Claude Code-style `.lsp.json` configuration files as defined in the [Claude Code plugins reference](https://code.claude.com/docs/en/plugins-reference#lsp-servers). If you're migrating from Claude Code, your existing LSP configuration should work with minimal changes.
|
||||
Qwen Code supports Claude Code-style `.lsp.json` configuration files in the language-keyed format defined in the [Claude Code plugins reference](https://code.claude.com/docs/en/plugins-reference#lsp-servers). If you're migrating from Claude Code, use the language-as-key layout in your configuration.
|
||||
|
||||
### Configuration Format
|
||||
|
||||
|
|
@ -364,19 +342,7 @@ The recommended format follows Claude Code's specification:
|
|||
}
|
||||
```
|
||||
|
||||
The `languageServers` wrapper format is also supported:
|
||||
|
||||
```json
|
||||
{
|
||||
"languageServers": {
|
||||
"gopls": {
|
||||
"languages": ["go"],
|
||||
"command": "gopls",
|
||||
"args": ["serve"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Claude Code LSP plugins can also supply `lspServers` in `plugin.json` (or a referenced `.lsp.json`). Qwen Code loads those configs when the extension is enabled, and they must use the same language-keyed format.
|
||||
|
||||
## Best Practices
|
||||
|
||||
|
|
|
|||
168
package-lock.json
generated
168
package-lock.json
generated
|
|
@ -596,6 +596,7 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
|
|
@ -619,6 +620,7 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -1933,6 +1935,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
|
|
@ -2447,7 +2450,6 @@
|
|||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^1.0.3",
|
||||
"is-glob": "^4.0.3",
|
||||
|
|
@ -2490,7 +2492,6 @@
|
|||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
|
|
@ -2512,7 +2513,6 @@
|
|||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
|
|
@ -2534,7 +2534,6 @@
|
|||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
|
|
@ -2556,7 +2555,6 @@
|
|||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
|
|
@ -2578,7 +2576,6 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
|
|
@ -2600,7 +2597,6 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
|
|
@ -2622,7 +2618,6 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
|
|
@ -2644,7 +2639,6 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
|
|
@ -2666,7 +2660,6 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
|
|
@ -2688,7 +2681,6 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
|
|
@ -2710,7 +2702,6 @@
|
|||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
|
|
@ -2732,7 +2723,6 @@
|
|||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
|
|
@ -2754,7 +2744,6 @@
|
|||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
|
|
@ -2770,7 +2759,6 @@
|
|||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"detect-libc": "bin/detect-libc.js"
|
||||
},
|
||||
|
|
@ -2784,8 +2772,7 @@
|
|||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
|
|
@ -3428,6 +3415,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
|
||||
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
|
|
@ -3878,19 +3866,13 @@
|
|||
"version": "2.4.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.4.9.tgz",
|
||||
"integrity": "sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"kleur": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/qrcode-terminal": {
|
||||
"version": "0.12.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/qrcode-terminal/-/qrcode-terminal-0.12.2.tgz",
|
||||
|
|
@ -3918,6 +3900,7 @@
|
|||
"integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
|
|
@ -3928,6 +3911,7 @@
|
|||
"integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.0.0"
|
||||
}
|
||||
|
|
@ -4133,6 +4117,7 @@
|
|||
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.35.0",
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
|
|
@ -4907,6 +4892,7 @@
|
|||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
|
|
@ -5314,8 +5300,7 @@
|
|||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/array-includes": {
|
||||
"version": "3.1.9",
|
||||
|
|
@ -5893,6 +5878,7 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.25",
|
||||
"caniuse-lite": "^1.0.30001754",
|
||||
|
|
@ -6645,7 +6631,6 @@
|
|||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
|
|
@ -7770,6 +7755,7 @@
|
|||
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
|
|
@ -8304,7 +8290,6 @@
|
|||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
|
|
@ -8366,7 +8351,6 @@
|
|||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
|
|
@ -8376,7 +8360,6 @@
|
|||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
|
|
@ -8386,7 +8369,6 @@
|
|||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
|
|
@ -8577,7 +8559,6 @@
|
|||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
|
||||
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~2.0.0",
|
||||
|
|
@ -8596,7 +8577,6 @@
|
|||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
|
|
@ -8605,15 +8585,13 @@
|
|||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/finalhandler/node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
|
|
@ -9602,8 +9580,7 @@
|
|||
"integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.1",
|
||||
|
|
@ -9700,6 +9677,7 @@
|
|||
"resolved": "https://registry.npmjs.org/ink/-/ink-6.2.3.tgz",
|
||||
"integrity": "sha512-fQkfEJjKbLXIcVWEE3MvpYSnwtbbmRsmeNDNz1pIuOFlwE+UF2gsy228J36OXKZGWJWZJKUigphBSqCNMcARtg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@alcalzone/ansi-tokenize": "^0.2.0",
|
||||
"ansi-escapes": "^7.0.0",
|
||||
|
|
@ -10695,6 +10673,7 @@
|
|||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
|
|
@ -11679,7 +11658,6 @@
|
|||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
|
|
@ -12980,8 +12958,7 @@
|
|||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
"version": "3.0.0",
|
||||
|
|
@ -13141,6 +13118,7 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
|
|
@ -13654,6 +13632,7 @@
|
|||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -13664,6 +13643,7 @@
|
|||
"integrity": "sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"shell-quote": "^1.6.1",
|
||||
"ws": "^7"
|
||||
|
|
@ -13697,6 +13677,7 @@
|
|||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
|
|
@ -14338,29 +14319,6 @@
|
|||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.94.2",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.94.2.tgz",
|
||||
"integrity": "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.0.2",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
||||
|
|
@ -15759,6 +15717,7 @@
|
|||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
|
@ -15938,7 +15897,8 @@
|
|||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
"license": "0BSD",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.20.3",
|
||||
|
|
@ -15946,6 +15906,7 @@
|
|||
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "~0.25.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
|
|
@ -16140,6 +16101,7 @@
|
|||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
|
@ -16448,7 +16410,6 @@
|
|||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
|
|
@ -16504,6 +16465,7 @@
|
|||
"integrity": "sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.6",
|
||||
|
|
@ -16617,6 +16579,7 @@
|
|||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
|
@ -16630,6 +16593,7 @@
|
|||
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/expect": "3.2.4",
|
||||
|
|
@ -17137,6 +17101,7 @@
|
|||
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
|
|
@ -17317,6 +17282,7 @@
|
|||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
|
@ -17329,7 +17295,6 @@
|
|||
"@iarna/toml": "^2.2.5",
|
||||
"@modelcontextprotocol/sdk": "^1.25.1",
|
||||
"@qwen-code/qwen-code-core": "file:../core",
|
||||
"@types/prompts": "^2.4.9",
|
||||
"@types/update-notifier": "^6.0.8",
|
||||
"ansi-regex": "^6.2.2",
|
||||
"command-exists": "^1.2.9",
|
||||
|
|
@ -17373,6 +17338,7 @@
|
|||
"@types/diff": "^7.0.2",
|
||||
"@types/dotenv": "^6.1.1",
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/prompts": "^2.4.9",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@types/semver": "^7.7.0",
|
||||
|
|
@ -17416,6 +17382,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.1.tgz",
|
||||
"integrity": "sha512-yO28oVFFC7EBoiKdAn+VqRm+plcfv4v0xp6osG/VsCB0NlPZWi87ajbCZZ8f/RvOFLEu7//rSRmuZZ7lMoe3gQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^1.19.7",
|
||||
"ajv": "^8.17.1",
|
||||
|
|
@ -18062,6 +18029,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.1.tgz",
|
||||
"integrity": "sha512-yO28oVFFC7EBoiKdAn+VqRm+plcfv4v0xp6osG/VsCB0NlPZWi87ajbCZZ8f/RvOFLEu7//rSRmuZZ7lMoe3gQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^1.19.7",
|
||||
"ajv": "^8.17.1",
|
||||
|
|
@ -18473,6 +18441,7 @@
|
|||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
|
@ -19235,6 +19204,7 @@
|
|||
"integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "7.18.0",
|
||||
"@typescript-eslint/types": "7.18.0",
|
||||
|
|
@ -19732,6 +19702,7 @@
|
|||
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
|
|
@ -20858,6 +20829,7 @@
|
|||
"integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/expect": "1.6.1",
|
||||
"@vitest/runner": "1.6.1",
|
||||
|
|
@ -21521,27 +21493,6 @@
|
|||
"zod": "^3.25 || ^4"
|
||||
}
|
||||
},
|
||||
"packages/vscode-ide-companion/node_modules/@types/react": {
|
||||
"version": "18.3.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
|
||||
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
},
|
||||
"packages/vscode-ide-companion/node_modules/@types/react-dom": {
|
||||
"version": "18.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
|
||||
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"packages/vscode-ide-companion/node_modules/@types/semver": {
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz",
|
||||
|
|
@ -21584,13 +21535,6 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"packages/vscode-ide-companion/node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"packages/vscode-ide-companion/node_modules/express": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
|
||||
|
|
@ -21671,40 +21615,6 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"packages/vscode-ide-companion/node_modules/react": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"packages/vscode-ide-companion/node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"packages/vscode-ide-companion/node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"packages/vscode-ide-companion/node_modules/send": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
|
||||
|
|
|
|||
78
packages/cli/src/services/lsp/LspConfigLoader.test.ts
Normal file
78
packages/cli/src/services/lsp/LspConfigLoader.test.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, afterEach } from 'vitest';
|
||||
import mock from 'mock-fs';
|
||||
import { LspConfigLoader } from './LspConfigLoader.js';
|
||||
import type { Extension } from '@qwen-code/qwen-code-core';
|
||||
|
||||
describe('LspConfigLoader extension configs', () => {
|
||||
const workspaceRoot = '/workspace';
|
||||
const extensionPath = '/extensions/ts-plugin';
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
it('loads inline lspServers config from extension', async () => {
|
||||
const loader = new LspConfigLoader(workspaceRoot);
|
||||
const extension = {
|
||||
name: 'ts-plugin',
|
||||
path: extensionPath,
|
||||
config: {
|
||||
lspServers: {
|
||||
typescript: {
|
||||
command: 'typescript-language-server',
|
||||
args: ['--stdio'],
|
||||
extensionToLanguage: {
|
||||
'.ts': 'typescript',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Extension;
|
||||
|
||||
const configs = await loader.loadExtensionConfigs([extension]);
|
||||
|
||||
expect(configs).toHaveLength(1);
|
||||
expect(configs[0]?.languages).toEqual(['typescript']);
|
||||
expect(configs[0]?.command).toBe('typescript-language-server');
|
||||
expect(configs[0]?.args).toEqual(['--stdio']);
|
||||
});
|
||||
|
||||
it('loads lspServers config from referenced file and hydrates variables', async () => {
|
||||
mock({
|
||||
[extensionPath]: {
|
||||
'.lsp.json': JSON.stringify({
|
||||
typescript: {
|
||||
command: 'typescript-language-server',
|
||||
args: ['--stdio'],
|
||||
env: {
|
||||
EXT_ROOT: '${CLAUDE_PLUGIN_ROOT}',
|
||||
},
|
||||
extensionToLanguage: {
|
||||
'.ts': 'typescript',
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const loader = new LspConfigLoader(workspaceRoot);
|
||||
const extension = {
|
||||
name: 'ts-plugin',
|
||||
path: extensionPath,
|
||||
config: {
|
||||
lspServers: './.lsp.json',
|
||||
},
|
||||
} as Extension;
|
||||
|
||||
const configs = await loader.loadExtensionConfigs([extension]);
|
||||
|
||||
expect(configs).toHaveLength(1);
|
||||
expect(configs[0]?.env?.EXT_ROOT).toBe(extensionPath);
|
||||
});
|
||||
});
|
||||
|
|
@ -7,6 +7,11 @@
|
|||
import * as fs from 'node:fs';
|
||||
import * as path from 'path';
|
||||
import { pathToFileURL } from 'url';
|
||||
import {
|
||||
recursivelyHydrateStrings,
|
||||
type Extension,
|
||||
type JsonValue,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import type {
|
||||
LspInitializationOptions,
|
||||
LspServerConfig,
|
||||
|
|
@ -18,9 +23,7 @@ export class LspConfigLoader {
|
|||
|
||||
/**
|
||||
* Load user .lsp.json configuration.
|
||||
* Supports two official formats:
|
||||
* 1. Basic format: { "language": { "command": "...", "extensionToLanguage": {...} } }
|
||||
* 2. LanguageServers format: { "languageServers": { "server-name": { "languages": [...], ... } } }
|
||||
* Supports basic format: { "language": { "command": "...", "extensionToLanguage": {...} } }
|
||||
*/
|
||||
async loadUserConfigs(): Promise<LspServerConfig[]> {
|
||||
const lspConfigPath = path.join(this.workspaceRoot, '.lsp.json');
|
||||
|
|
@ -39,10 +42,76 @@ export class LspConfigLoader {
|
|||
}
|
||||
|
||||
/**
|
||||
* Merge configs: built-in presets + user configs + compatibility layer
|
||||
* Load LSP configurations declared by extensions (Claude plugins).
|
||||
*/
|
||||
async loadExtensionConfigs(extensions: Extension[]): Promise<LspServerConfig[]> {
|
||||
const configs: LspServerConfig[] = [];
|
||||
|
||||
for (const extension of extensions) {
|
||||
const lspServers = extension.config?.lspServers;
|
||||
if (!lspServers) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const originBase = `extension ${extension.name}`;
|
||||
if (typeof lspServers === 'string') {
|
||||
const configPath = this.resolveExtensionConfigPath(
|
||||
extension.path,
|
||||
lspServers,
|
||||
);
|
||||
if (!fs.existsSync(configPath)) {
|
||||
console.warn(
|
||||
`LSP config not found for ${originBase}: ${configPath}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const configContent = fs.readFileSync(configPath, 'utf-8');
|
||||
const data = JSON.parse(configContent) as JsonValue;
|
||||
const hydrated = this.hydrateExtensionLspConfig(
|
||||
data,
|
||||
extension.path,
|
||||
);
|
||||
configs.push(
|
||||
...this.parseConfigSource(
|
||||
hydrated,
|
||||
`${originBase} (${configPath})`,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`Failed to load extension LSP config from ${configPath}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
} else if (this.isRecord(lspServers)) {
|
||||
const hydrated = this.hydrateExtensionLspConfig(
|
||||
lspServers as JsonValue,
|
||||
extension.path,
|
||||
);
|
||||
configs.push(
|
||||
...this.parseConfigSource(
|
||||
hydrated,
|
||||
`${originBase} (lspServers)`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
console.warn(
|
||||
`LSP config for ${originBase} must be an object or a JSON file path.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return configs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge configs: built-in presets + extension configs + user configs
|
||||
*/
|
||||
mergeConfigs(
|
||||
detectedLanguages: string[],
|
||||
extensionConfigs: LspServerConfig[],
|
||||
userConfigs: LspServerConfig[],
|
||||
): LspServerConfig[] {
|
||||
// Built-in preset configurations
|
||||
|
|
@ -51,17 +120,22 @@ export class LspConfigLoader {
|
|||
// Merge configs, user configs take priority
|
||||
const mergedConfigs = [...presets];
|
||||
|
||||
for (const userConfig of userConfigs) {
|
||||
// Find if there's a preset with the same name, if so replace it
|
||||
const existingIndex = mergedConfigs.findIndex(
|
||||
(c) => c.name === userConfig.name,
|
||||
);
|
||||
if (existingIndex !== -1) {
|
||||
mergedConfigs[existingIndex] = userConfig;
|
||||
} else {
|
||||
mergedConfigs.push(userConfig);
|
||||
const applyConfigs = (configs: LspServerConfig[]) => {
|
||||
for (const config of configs) {
|
||||
// Find if there's a preset with the same name, if so replace it
|
||||
const existingIndex = mergedConfigs.findIndex(
|
||||
(c) => c.name === config.name,
|
||||
);
|
||||
if (existingIndex !== -1) {
|
||||
mergedConfigs[existingIndex] = config;
|
||||
} else {
|
||||
mergedConfigs.push(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
applyConfigs(extensionConfigs);
|
||||
applyConfigs(userConfigs);
|
||||
|
||||
return mergedConfigs;
|
||||
}
|
||||
|
|
@ -155,7 +229,7 @@ export class LspConfigLoader {
|
|||
|
||||
/**
|
||||
* Parse configuration source and extract server configs.
|
||||
* Detects format based on presence of 'languageServers' key.
|
||||
* Expects basic format keyed by language identifier.
|
||||
*/
|
||||
private parseConfigSource(
|
||||
source: unknown,
|
||||
|
|
@ -167,31 +241,15 @@ export class LspConfigLoader {
|
|||
|
||||
const configs: LspServerConfig[] = [];
|
||||
|
||||
// Determine format: languageServers wrapper vs basic format
|
||||
const hasLanguageServersWrapper = this.isRecord(source['languageServers']);
|
||||
const serverMap = hasLanguageServersWrapper
|
||||
? (source['languageServers'] as Record<string, unknown>)
|
||||
: source;
|
||||
|
||||
for (const [key, spec] of Object.entries(serverMap)) {
|
||||
for (const [key, spec] of Object.entries(source)) {
|
||||
if (!this.isRecord(spec)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// In basic format: key is language name, server name comes from command
|
||||
// In languageServers format: key is server name, languages come from 'languages' array
|
||||
const isBasicFormat = !hasLanguageServersWrapper && !spec['languages'];
|
||||
|
||||
const languages = isBasicFormat
|
||||
? [key]
|
||||
: (this.normalizeStringArray(spec['languages']) ??
|
||||
(typeof spec['languages'] === 'string' ? [spec['languages']] : []));
|
||||
|
||||
const name = isBasicFormat
|
||||
? typeof spec['command'] === 'string'
|
||||
? spec['command']
|
||||
: key
|
||||
: key;
|
||||
// In basic format: key is language name, server name comes from command.
|
||||
const languages = [key];
|
||||
const name =
|
||||
typeof spec['command'] === 'string' ? (spec['command'] as string) : key;
|
||||
|
||||
const config = this.buildServerConfig(name, languages, spec, origin);
|
||||
if (config) {
|
||||
|
|
@ -202,6 +260,28 @@ export class LspConfigLoader {
|
|||
return configs;
|
||||
}
|
||||
|
||||
private resolveExtensionConfigPath(
|
||||
extensionPath: string,
|
||||
configPath: string,
|
||||
): string {
|
||||
return path.isAbsolute(configPath)
|
||||
? path.resolve(configPath)
|
||||
: path.resolve(extensionPath, configPath);
|
||||
}
|
||||
|
||||
private hydrateExtensionLspConfig(
|
||||
source: JsonValue,
|
||||
extensionPath: string,
|
||||
): JsonValue {
|
||||
return recursivelyHydrateStrings(source, {
|
||||
extensionPath,
|
||||
CLAUDE_PLUGIN_ROOT: extensionPath,
|
||||
workspacePath: this.workspaceRoot,
|
||||
'/': path.sep,
|
||||
pathSeparator: path.sep,
|
||||
});
|
||||
}
|
||||
|
||||
private buildServerConfig(
|
||||
name: string,
|
||||
languages: string[],
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import type {
|
|||
LspSymbolInformation,
|
||||
LspTextEdit,
|
||||
LspWorkspaceEdit,
|
||||
Extension,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import type { EventEmitter } from 'events';
|
||||
import { LspConfigLoader } from './LspConfigLoader.js';
|
||||
|
|
@ -98,19 +99,35 @@ export class NativeLspService {
|
|||
|
||||
// Detect languages in workspace
|
||||
const userConfigs = await this.configLoader.loadUserConfigs();
|
||||
const extensionConfigs = await this.configLoader.loadExtensionConfigs(
|
||||
this.getActiveExtensions(),
|
||||
);
|
||||
const extensionOverrides =
|
||||
this.configLoader.collectExtensionToLanguageOverrides(userConfigs);
|
||||
this.configLoader.collectExtensionToLanguageOverrides([
|
||||
...extensionConfigs,
|
||||
...userConfigs,
|
||||
]);
|
||||
const detectedLanguages =
|
||||
await this.languageDetector.detectLanguages(extensionOverrides);
|
||||
|
||||
// Merge configs: built-in presets + user .lsp.json + optional cclsp compatibility
|
||||
// Merge configs: built-in presets + extension LSP configs + user .lsp.json
|
||||
const serverConfigs = this.configLoader.mergeConfigs(
|
||||
detectedLanguages,
|
||||
extensionConfigs,
|
||||
userConfigs,
|
||||
);
|
||||
this.serverManager.setServerConfigs(serverConfigs);
|
||||
}
|
||||
|
||||
private getActiveExtensions(): Extension[] {
|
||||
const configWithExtensions = this.config as unknown as {
|
||||
getActiveExtensions?: () => Extension[];
|
||||
};
|
||||
return typeof configWithExtensions.getActiveExtensions === 'function'
|
||||
? configWithExtensions.getActiveExtensions()
|
||||
: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Start all LSP servers
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -43,6 +43,26 @@ describe('convertClaudeToQwenConfig', () => {
|
|||
expect(result.mcpServers).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should preserve lspServers configuration', () => {
|
||||
const claudeConfig: ClaudePluginConfig = {
|
||||
name: 'lsp-plugin',
|
||||
version: '1.0.0',
|
||||
lspServers: {
|
||||
typescript: {
|
||||
command: 'typescript-language-server',
|
||||
args: ['--stdio'],
|
||||
extensionToLanguage: {
|
||||
'.ts': 'typescript',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = convertClaudeToQwenConfig(claudeConfig);
|
||||
|
||||
expect(result.lspServers).toEqual(claudeConfig.lspServers);
|
||||
});
|
||||
|
||||
it('should throw error for missing name', () => {
|
||||
const invalidConfig = {
|
||||
version: '1.0.0',
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export interface ClaudePluginConfig {
|
|||
hooks?: string;
|
||||
mcpServers?: string | Record<string, MCPServerConfig>;
|
||||
outputStyles?: string | string[];
|
||||
lspServers?: string;
|
||||
lspServers?: string | Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -318,17 +318,12 @@ export function convertClaudeToQwenConfig(
|
|||
`[Claude Converter] Output styles are not yet supported in ${claudeConfig.name}`,
|
||||
);
|
||||
}
|
||||
if (claudeConfig.lspServers) {
|
||||
console.warn(
|
||||
`[Claude Converter] LSP servers are not yet supported in ${claudeConfig.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Direct field mapping - commands, skills, agents will be collected as folders
|
||||
return {
|
||||
name: claudeConfig.name,
|
||||
version: claudeConfig.version,
|
||||
mcpServers,
|
||||
lspServers: claudeConfig.lspServers,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ export interface ExtensionConfig {
|
|||
name: string;
|
||||
version: string;
|
||||
mcpServers?: Record<string, MCPServerConfig>;
|
||||
lspServers?: string | Record<string, unknown>;
|
||||
contextFileName?: string | string[];
|
||||
commands?: string | string[];
|
||||
skills?: string | string[];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue