157 lines
4.7 KiB
Rust
157 lines
4.7 KiB
Rust
//! Periodically shuts down idle LSP servers.
|
|
//!
|
|
//! Every `SCAN_INTERVAL` this singleton scans for workspaces that have a running LSP server
|
|
//! but no associated:
|
|
//! 1. Active local `TerminalView` whose repo root matches the workspace root.
|
|
//! 2. Open `LocalCodeEditorView` with a file that belongs to that workspace root.
|
|
//!
|
|
//! For those workspaces, we call `LspManagerModel::stop` to tear down the LSP server.
|
|
|
|
use std::path::{Path, PathBuf};
|
|
use std::time::Duration;
|
|
|
|
use futures::stream::AbortHandle;
|
|
use galaxyui::r#async::Timer;
|
|
use galaxyui::{AppContext, Entity, ModelContext, SingletonEntity};
|
|
use lsp::LspManagerModel;
|
|
|
|
use crate::code::local_code_editor::LocalCodeEditorView;
|
|
use crate::terminal::TerminalView;
|
|
|
|
const SCAN_INTERVAL: Duration = Duration::from_secs(10);
|
|
|
|
pub struct LanguageServerShutdownManager {
|
|
in_progress_scan: Option<AbortHandle>,
|
|
}
|
|
|
|
impl LanguageServerShutdownManager {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
in_progress_scan: None,
|
|
}
|
|
}
|
|
|
|
pub fn has_in_progress_scan(&self) -> bool {
|
|
self.in_progress_scan.is_some()
|
|
}
|
|
|
|
pub fn schedule_next_scan(&mut self, ctx: &mut ModelContext<Self>) {
|
|
if let Some(scan) = self.in_progress_scan.take() {
|
|
scan.abort();
|
|
}
|
|
|
|
self.in_progress_scan = Some(
|
|
ctx.spawn(
|
|
async {
|
|
Timer::after(SCAN_INTERVAL).await;
|
|
},
|
|
|me, _, ctx| {
|
|
if me.scan_and_shutdown_unused_servers(ctx) {
|
|
me.schedule_next_scan(ctx);
|
|
} else {
|
|
me.in_progress_scan = None;
|
|
}
|
|
},
|
|
)
|
|
.abort_handle(),
|
|
);
|
|
}
|
|
|
|
/// Scans for unused LSP servers and shuts them down.
|
|
///
|
|
/// Returns `true` if there are still active workspace roots remaining (indicating more scans
|
|
/// may be needed), or `false` if all workspace roots were shut down or there were no roots.
|
|
fn scan_and_shutdown_unused_servers(&self, ctx: &mut ModelContext<Self>) -> bool {
|
|
let lsp_manager_handle = LspManagerModel::handle(ctx);
|
|
let workspace_roots: Vec<PathBuf> = lsp_manager_handle
|
|
.as_ref(ctx)
|
|
.workspace_roots()
|
|
.cloned()
|
|
.collect();
|
|
|
|
if workspace_roots.is_empty() {
|
|
return false;
|
|
}
|
|
|
|
let mut unused_roots = Vec::new();
|
|
for root in &workspace_roots {
|
|
if !workspace_root_in_use(root, ctx) {
|
|
unused_roots.push(root);
|
|
}
|
|
}
|
|
|
|
// There will be remaining active roots if not all of the workspace roots are unused.
|
|
let has_active_roots = unused_roots.len() < workspace_roots.len();
|
|
|
|
if unused_roots.is_empty() {
|
|
return has_active_roots;
|
|
}
|
|
|
|
// Stop servers for all workspaces that are no longer in use.
|
|
lsp_manager_handle.update(ctx, |manager, m_ctx| {
|
|
for root in unused_roots {
|
|
log::info!("Stopping unused LSP for workspace {}", root.display());
|
|
manager.stop_all(root.clone(), m_ctx);
|
|
}
|
|
});
|
|
|
|
has_active_roots
|
|
}
|
|
}
|
|
|
|
fn workspace_root_in_use(root: &Path, app: &AppContext) -> bool {
|
|
has_terminal_for_workspace(root, app) || has_open_file_for_workspace(root, app)
|
|
}
|
|
|
|
fn has_terminal_for_workspace(root: &Path, app: &AppContext) -> bool {
|
|
for window_id in app.window_ids() {
|
|
if let Some(terminals) = app.views_of_type::<TerminalView>(window_id) {
|
|
for terminal in terminals {
|
|
let Some(pwd) = terminal.as_ref(app).pwd_if_local(app) else {
|
|
continue;
|
|
};
|
|
|
|
let Ok(cwd) = PathBuf::from(pwd).canonicalize() else {
|
|
continue;
|
|
};
|
|
|
|
if cwd.starts_with(root) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
fn has_open_file_for_workspace(root: &Path, app: &AppContext) -> bool {
|
|
for window_id in app.window_ids() {
|
|
if let Some(editors) = app.views_of_type::<LocalCodeEditorView>(window_id) {
|
|
for editor in editors {
|
|
let editor_ref = editor.as_ref(app);
|
|
|
|
if !editor_ref.language_server_enabled() {
|
|
continue;
|
|
}
|
|
|
|
let Some(path) = editor_ref.file_path() else {
|
|
continue;
|
|
};
|
|
|
|
if path.starts_with(root) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
impl Entity for LanguageServerShutdownManager {
|
|
type Event = ();
|
|
}
|
|
|
|
impl SingletonEntity for LanguageServerShutdownManager {}
|