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:
+45
-2
@@ -101,7 +101,7 @@ impl AppStateRest {
|
||||
tracing::warn!("[state] session_dir has no file_name component, using empty session_id");
|
||||
String::new()
|
||||
});
|
||||
AppStateRest {
|
||||
let state = AppStateRest {
|
||||
|
||||
settings,
|
||||
app_config,
|
||||
@@ -127,7 +127,41 @@ impl AppStateRest {
|
||||
misc: MiscState::new(),
|
||||
dirty: true,
|
||||
quit: false,
|
||||
};
|
||||
|
||||
// Fire-and-forget background LSP provisioning.
|
||||
//
|
||||
// Flow: spawn OS thread -> provision_all() probes/installs every
|
||||
// supported language server -> auto_connect() attaches whichever
|
||||
// ones ended up available to the shared `lsp_manager` -> log a line
|
||||
// per connected server and per failure.
|
||||
//
|
||||
// Why a raw thread and not a tokio task: this runs before the async
|
||||
// runtime's executor may be fully set up for this state, and the
|
||||
// provisioning work (shelling out to package managers, network
|
||||
// downloads) is blocking I/O; a dedicated thread keeps it off any
|
||||
// async executor entirely. It is deliberately not joined -- startup
|
||||
// must not block on language server installation, and failures are
|
||||
// logged rather than surfaced, since editing still works without LSP.
|
||||
if state.settings.lsp_auto_provision {
|
||||
let lsp_mgr = state.lsp_manager.clone();
|
||||
std::thread::spawn(move || {
|
||||
use crate::app::lsp::provisioner::{self, ProvisionResult};
|
||||
|
||||
let results = provisioner::provision_all();
|
||||
let connected = provisioner::auto_connect(&lsp_mgr, &results);
|
||||
for name in &connected {
|
||||
tracing::info!("LSP: {} connected", name);
|
||||
}
|
||||
for r in &results {
|
||||
if let ProvisionResult::Failed { language, server_name, reason, .. } = r {
|
||||
tracing::warn!("LSP {} ({}): {}", server_name, language, reason);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
/// Whether an agent turn is currently running.
|
||||
@@ -141,7 +175,16 @@ impl AppStateRest {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/// Shut down every running LSP server process.
|
||||
///
|
||||
/// Why: called on app exit so language servers don't linger as orphaned
|
||||
/// processes; silently no-ops if the mutex is poisoned since there is
|
||||
/// nothing more useful to do at shutdown time.
|
||||
pub fn shutdown_lsp(&mut self) {
|
||||
if let Ok(mut mgr) = self.lsp_manager.lock() {
|
||||
mgr.shutdown_all();
|
||||
}
|
||||
}
|
||||
|
||||
/// Append a message to the transcript, evicting the oldest entry once
|
||||
/// `max_lines` is exceeded, and mark both the cache and the app dirty.
|
||||
|
||||
Reference in New Issue
Block a user