From 62f5ca562ea5d1316fda6cc2ebc0ac9313de0710 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 20 Dec 2024 19:41:23 +0100 Subject: [PATCH] editor: Improve performance of lsp_ext validation (#22299) We've received a complaint on Discord about bad multicursor performance. I too run 15k cursor simultaneously. The gist of the issue was in the lsp_ext; whenever we gather up actions to be registered on a buffer, we need to know whether a buffer has any of the languages for which we have LSP extensions. The problem stemed from the fact that we did a two-phase filtering. For each selection we'd first check whether this selection lies in a part of a file that is part of a language for which we have LSP extensions. Then, we'd check whether we're running a language server of interest for this buffer. This is not optimal, because it would often do the redundant work: 1. We resolve selections for buffer that are known to not contain a given language server. 2. We look up language server in the LspStore once per each matching selection. In case where the file is not related at all, we end up resolving all of the selections which is pretty bad. This PR makes us skip buffers which are known to not match the criteria. It also caches the result of language server lookup for the buffers. Closes #ISSUE Release Notes: - Improved performance with large quantity of cursors --- crates/editor/src/lsp_ext.rs | 48 ++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/crates/editor/src/lsp_ext.rs b/crates/editor/src/lsp_ext.rs index 4265002366463b022c0a53d558bdde15e16d3a83..2937e75943970cf3f7011b053daecff6ccd961ca 100644 --- a/crates/editor/src/lsp_ext.rs +++ b/crates/editor/src/lsp_ext.rs @@ -1,6 +1,8 @@ +use std::collections::hash_map::Entry; use std::sync::Arc; use crate::Editor; +use collections::HashMap; use gpui::{Model, WindowContext}; use language::Buffer; use language::Language; @@ -20,6 +22,7 @@ where return None; }; let multibuffer = editor.buffer().read(cx); + let mut language_servers_for = HashMap::default(); editor .selections .disjoint_anchors() @@ -28,27 +31,36 @@ where .filter_map(|selection| Some((selection.start.buffer_id?, selection.start))) .filter_map(|(buffer_id, trigger_anchor)| { let buffer = multibuffer.buffer(buffer_id)?; + let server_id = *match language_servers_for.entry(buffer_id) { + Entry::Occupied(occupied_entry) => occupied_entry.into_mut(), + Entry::Vacant(vacant_entry) => { + let language_server_id = project + .read(cx) + .language_servers_for_local_buffer(buffer.read(cx), cx) + .find_map(|(adapter, server)| { + if adapter.name.0.as_ref() == language_server_name { + Some(server.server_id()) + } else { + None + } + }); + vacant_entry.insert(language_server_id) + } + } + .as_ref()?; + + Some((buffer, trigger_anchor, server_id)) + }) + .find_map(|(buffer, trigger_anchor, server_id)| { let language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?; if !filter_language(&language) { return None; } - Some((trigger_anchor, language, buffer)) - }) - .find_map(|(trigger_anchor, language, buffer)| { - project - .read(cx) - .language_servers_for_local_buffer(buffer.read(cx), cx) - .find_map(|(adapter, server)| { - if adapter.name.0.as_ref() == language_server_name { - Some(( - trigger_anchor, - Arc::clone(&language), - server.server_id(), - buffer.clone(), - )) - } else { - None - } - }) + Some(( + trigger_anchor, + Arc::clone(&language), + server_id, + buffer.clone(), + )) }) }