code-yeongyu / code-yeongyu/pi-lsp-client

Feature request: configurable multi-server routing and compound file suffix support

Open
#3 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
16
Forks
5
PR merge metrics
No merged PRs in 30d

Description

## Motivation

Would you be open to a PR that extends the existing custom-server configuration with generic multi-server routing and compound file suffix support?

The current configuration works well when one language server owns a normal extension such as `.ts`, `.php`, or `.rs`. It becomes limiting when:

1. Multiple language servers support the same file type but provide different capabilities.
2. A compound suffix needs different handling from its final extension.

Examples include:

* A general PHP language server and a framework-aware PHP language server both handling `.php`.
* `.blade.php` requiring different routing from ordinary `.php`.
* `.html.erb` requiring different routing from `.erb`.
* A general TypeScript server plus another server used selectively for diagnostics or framework intelligence.

This proposal would remain completely framework-agnostic. Laravel and Blade would only be one configuration example.

## Proposed behavior

### 1. Optional `serverId` on LSP tools

Each LSP tool could accept an optional `serverId`:

```json
{
"filePath": "src/example.php",
"line": 12,
"character": 8,
"serverId": "framework-aware"
}
```

When `serverId` is omitted, the existing automatic priority-based resolution remains the default.

When provided, the client selects that specific configured server after verifying that:

* the server exists;
* it supports the requested file;
* its executable is installed.

This would preserve backward compatibility while allowing agents to choose between multiple servers intentionally.

### 2. Longest-suffix matching

Instead of resolving files solely from the final extension, server selection would choose the longest matching configured suffix.

For example:

```json
{
"lsp": {
"general-php": {
"command": ["general-php-lsp", "--stdio"],
"extensions": [".php"],
"priority": 200
},
"template-aware": {
"command": ["template-lsp", "--stdio"],
"extensions": [".template.php", ".php"],
"priority": 100
}
}
}
```

Expected routing:

```text
src/Example.php
→ both servers are eligible
→ general-php wins by priority

views/show.template.php
→ .template.php is the longest suffix
→ only template-aware is eligible
```

This prevents a server configured only for `.php` from accidentally claiming every compound `.something.php` file.

### 3. Configurable language IDs

Allow each server to map suffixes to LSP language IDs:

```json
{
"languageIds": {
".php": "php",
".template.php": "template"
}
}
```

This is necessary because a file suffix and the `textDocument.languageId` sent to the language server are not always identical.

### 4. Preserve global and project configuration

The existing locations would continue to work:

```text
~/.pi/lsp-client.json
/.pi/lsp-client.json
```

Suggested merge order:

```text
built-ins < global configuration < project configuration
```

A project could therefore add or override specialized routing without changing the user's global setup.

### 5. Additional generic read tools

The same PR could expose two standard LSP capabilities that are useful for framework-aware and template-aware servers:

* `lsp_hover`
* `lsp_document_links`

`textDocument/documentLink` is particularly useful for language servers that expose navigation for routes, templates, configuration keys, imports, or other references that are not normal document symbols.

These tools would also accept optional `serverId`.

## Example framework configuration

This would be configuration only, with no framework-specific logic in the core:

```json
{
"lsp": {
"phpactor": {
"command": ["phpactor", "language-server"],
"extensions": [".php"],
"languageIds": {
".php": "php"
},
"priority": 200
},
"laravel": {
"command": ["laravel-lsp"],
"extensions": [".blade.php", ".php"],
"languageIds": {
".blade.php": "blade",
".php": "php"
},
"priority": 100
}
}
}
```

Result:

```text
Ordinary .php
→ Phpactor by default

Ordinary .php with serverId="laravel"
→ Laravel LSP explicitly

.blade.php
→ Laravel LSP automatically through longest-suffix matching
```

The same mechanism would work for unrelated frameworks and languages.

## Backward compatibility

Existing configurations without `serverId` or `languageIds` should continue to behave as they do now:

* normal extension matching;
* automatic priority-based selection;
* default language ID inferred from the suffix;
* lazy server startup and the existing shared server lifecycle.

## Implementation scope

The proposed PR would include:

* optional `serverId` in tool schemas;
* server resolution by explicit ID or existing priority behavior;
* longest-suffix matching;
* suffix-to-language-ID configuration;
* hover support;
* document-link support;
* tests for normal extensions, compound suffixes, explicit routing, and fallback behavior;
* documentation with generic examples.

I have a working proof of concept locally and can prepare a focused PR if this direction fits the project's intended scope.

Would you prefer this as one PR, or should the routing changes and additional LSP tools be split into separate PRs?

Contributor guide

No contributing guide indexed for this repository

Research direction

The issue does not name files or existing tests. Start by locating the custom-server configuration, server-resolution logic, LSP tool schemas, and shared server lifecycle, then trace how extension matching and language IDs currently work. Done would require the proposed routing, tools, backward-compatible behavior, tests, and documentation.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
developer-experience, devtools, tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.