Limit LSP non-invalidating queries

Kirill Bulatov created

Change summary

crates/editor/src/inlay_hint_cache.rs | 40 ++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)

Detailed changes

crates/editor/src/inlay_hint_cache.rs 🔗

@@ -19,6 +19,7 @@ use project::{InlayHint, ResolveState};
 
 use collections::{hash_map, HashMap, HashSet};
 use language::language_settings::InlayHintSettings;
+use smol::lock::Semaphore;
 use sum_tree::Bias;
 use text::ToOffset;
 use util::post_inc;
@@ -29,6 +30,7 @@ pub struct InlayHintCache {
     version: usize,
     pub(super) enabled: bool,
     update_tasks: HashMap<ExcerptId, TasksForRanges>,
+    lsp_request_limiter: Arc<Semaphore>,
 }
 
 #[derive(Debug)]
@@ -258,6 +260,7 @@ impl InlayHintCache {
             hints: HashMap::default(),
             update_tasks: HashMap::default(),
             version: 0,
+            lsp_request_limiter: Arc::new(Semaphore::new(MAX_CONCURRENT_LSP_REQUESTS)),
         }
     }
 
@@ -629,6 +632,7 @@ fn spawn_new_update_tasks(
                 buffer_snapshot.clone(),
                 Arc::clone(&visible_hints),
                 cached_excerpt_hints,
+                Arc::clone(&editor.inlay_hint_cache.lsp_request_limiter),
                 cx,
             )
         };
@@ -733,6 +737,7 @@ fn determine_query_ranges(
     })
 }
 
+const MAX_CONCURRENT_LSP_REQUESTS: usize = 5;
 const INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS: u64 = 400;
 
 fn new_update_task(
@@ -742,6 +747,7 @@ fn new_update_task(
     buffer_snapshot: BufferSnapshot,
     visible_hints: Arc<Vec<Inlay>>,
     cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
+    lsp_request_limiter: Arc<Semaphore>,
     cx: &mut ViewContext<'_, '_, Editor>,
 ) -> Task<()> {
     cx.spawn(|editor, mut cx| async move {
@@ -756,6 +762,7 @@ fn new_update_task(
                 query,
                 invalidate,
                 range,
+                Arc::clone(&lsp_request_limiter),
                 closure_cx.clone(),
             )
         };
@@ -826,10 +833,41 @@ async fn fetch_and_update_hints(
     query: ExcerptQuery,
     invalidate: bool,
     fetch_range: Range<language::Anchor>,
+    lsp_request_limiter: Arc<Semaphore>,
     mut cx: gpui::AsyncAppContext,
 ) -> anyhow::Result<()> {
+    let (lsp_request_guard, got_throttled) = if query.invalidate.should_invalidate() {
+        (None, false)
+    } else {
+        match lsp_request_limiter.try_acquire() {
+            Some(guard) => (Some(guard), false),
+            None => (Some(lsp_request_limiter.acquire().await), true),
+        }
+    };
     let inlay_hints_fetch_task = editor
         .update(&mut cx, |editor, cx| {
+            if got_throttled {
+                if let Some((_, _, current_visible_range)) = editor
+                    .excerpt_visible_offsets(None, cx)
+                    .remove(&query.excerpt_id)
+                {
+                    let visible_offset_length = current_visible_range.len();
+                    let double_visible_range = current_visible_range
+                        .start
+                        .saturating_sub(visible_offset_length)
+                        ..current_visible_range
+                            .end
+                            .saturating_add(visible_offset_length)
+                            .min(buffer_snapshot.len());
+                    if !double_visible_range
+                        .contains(&fetch_range.start.to_offset(&buffer_snapshot))
+                        && !double_visible_range
+                            .contains(&fetch_range.end.to_offset(&buffer_snapshot))
+                    {
+                        return None;
+                    }
+                }
+            }
             editor
                 .buffer()
                 .read(cx)
@@ -847,6 +885,8 @@ async fn fetch_and_update_hints(
         Some(task) => task.await.context("inlay hint fetch task")?,
         None => return Ok(()),
     };
+    drop(lsp_request_guard);
+
     let background_task_buffer_snapshot = buffer_snapshot.clone();
     let backround_fetch_range = fetch_range.clone();
     let new_update = cx