feat(lsp): implement auto-provisioning for language servers
- Added LSP auto-provisioning functionality to automatically install and connect language servers. - Introduced `shutdown_lsp` method to cleanly shut down LSP servers on application exit. - Enhanced `AppStateRest` to spawn a background thread for provisioning language servers. - Updated `builtin_agents` to include new LSP-related agents. - Modified settings to include options for LSP auto-provisioning and supported languages. - Updated file editing and writing tools to notify LSP servers of changes. - Enhanced LSP tools to support auto-detection of servers based on file extensions. - Added utility functions for managing known file extensions and resolving server names. - Created a new `provisioner` module to handle the provisioning logic for various language servers.
This commit is contained in:
@@ -311,6 +311,59 @@ impl LspClient {
|
||||
}
|
||||
}
|
||||
|
||||
/// Health-check the LSP server.
|
||||
///
|
||||
/// Sends a `textDocument/documentSymbol` request on a dummy URI with a
|
||||
/// 2-second timeout. Returns `true` if the server responds at all —
|
||||
/// including with an error response such as "file not found", which
|
||||
/// still proves the process is up and the JSON-RPC channel is live.
|
||||
/// Returns `false` on timeout, EOF, or any read/write error.
|
||||
///
|
||||
/// Flow: build request → send_frame → poll frames until id matches
|
||||
/// (alive) or deadline/read error fires (dead).
|
||||
#[allow(dead_code)]
|
||||
pub fn is_alive(&mut self) -> bool {
|
||||
self.next_id += 1;
|
||||
let id = self.next_id;
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": id,
|
||||
"method": "textDocument/documentSymbol",
|
||||
"params": {
|
||||
"textDocument": { "uri": "file:///__zesdex_lsp_health_check__.txt" }
|
||||
}
|
||||
});
|
||||
if self.send_frame(&req).is_err() {
|
||||
return false;
|
||||
}
|
||||
let timeout = Duration::from_secs(2);
|
||||
let deadline = Instant::now() + timeout;
|
||||
loop {
|
||||
if Instant::now() > deadline {
|
||||
return false;
|
||||
}
|
||||
match self.read_frame() {
|
||||
Ok(frame) => {
|
||||
if frame.get("id") == Some(&json!(id)) {
|
||||
return true;
|
||||
}
|
||||
// Skip unrelated notifications/responses on the same channel.
|
||||
}
|
||||
Err(_) => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Send the LSP `exit` notification to request graceful shutdown.
|
||||
///
|
||||
/// Per the LSP spec, `exit` is a notification — the server is expected
|
||||
/// to terminate after receiving it without sending a response. We do
|
||||
/// not block on any reply.
|
||||
#[allow(dead_code)]
|
||||
pub fn exit(&mut self) -> anyhow::Result<()> {
|
||||
self.notify("exit", json!({}))
|
||||
}
|
||||
|
||||
pub fn shutdown(&mut self) -> anyhow::Result<()> {
|
||||
let _ = self.call_with_timeout("shutdown", json!({}), Duration::from_secs(5));
|
||||
let _ = self.notify("exit", json!({}));
|
||||
|
||||
Reference in New Issue
Block a user