Request hints for all buffers in editor

This commit is contained in:
Kirill Bulatov 2023-06-02 23:04:27 +03:00
parent f83cfda9bc
commit 387415eb01

View file

@ -22,11 +22,11 @@ pub mod test;
use ::git::diff::DiffHunk; use ::git::diff::DiffHunk;
use aho_corasick::AhoCorasick; use aho_corasick::AhoCorasick;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Context, Result};
use blink_manager::BlinkManager; use blink_manager::BlinkManager;
use client::{ClickhouseEvent, TelemetrySettings}; use client::{ClickhouseEvent, TelemetrySettings};
use clock::ReplicaId; use clock::{Global, ReplicaId};
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use collections::{hash_map, BTreeMap, Bound, HashMap, HashSet, VecDeque};
use copilot::Copilot; use copilot::Copilot;
pub use display_map::DisplayPoint; pub use display_map::DisplayPoint;
use display_map::*; use display_map::*;
@ -64,6 +64,7 @@ use language::{
use link_go_to_definition::{ use link_go_to_definition::{
hide_link_definition, show_link_definition, LinkDefinitionKind, LinkGoToDefinitionState, hide_link_definition, show_link_definition, LinkDefinitionKind, LinkGoToDefinitionState,
}; };
use log::error;
pub use multi_buffer::{ pub use multi_buffer::{
Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset, Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
ToPoint, ToPoint,
@ -90,10 +91,7 @@ use std::{
num::NonZeroU32, num::NonZeroU32,
ops::{Deref, DerefMut, Range}, ops::{Deref, DerefMut, Range},
path::Path, path::Path,
sync::{ sync::Arc,
atomic::{self, AtomicUsize},
Arc,
},
time::{Duration, Instant}, time::{Duration, Instant},
}; };
pub use sum_tree::Bias; pub use sum_tree::Bias;
@ -1159,43 +1157,53 @@ impl CopilotState {
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct InlayHintState { struct InlayHintState(RwLock<(HashMap<usize, Global>, Vec<InlayHint>)>);
hints: RwLock<Vec<InlayHint>>,
last_updated_timestamp: AtomicUsize,
hints_generation: AtomicUsize,
}
impl InlayHintState { impl InlayHintState {
pub fn new_timestamp(&self) -> usize { fn read(&self) -> Vec<InlayHint> {
self.hints_generation self.0.read().1.clone()
.fetch_add(1, atomic::Ordering::Release)
+ 1
} }
pub fn read(&self) -> Vec<InlayHint> { fn is_newer(&self, timestamp: &HashMap<usize, Global>) -> bool {
self.hints.read().clone() let current_timestamp = self.0.read().0.clone();
Self::first_timestamp_newer(timestamp, &current_timestamp)
} }
pub fn update_if_newer(&self, new_hints: Vec<InlayHint>, new_timestamp: usize) { fn update_if_newer(&self, new_hints: Vec<InlayHint>, new_timestamp: HashMap<usize, Global>) {
let last_updated_timestamp = self.last_updated_timestamp.load(atomic::Ordering::Acquire); let mut guard = self.0.write();
dbg!(last_updated_timestamp, new_timestamp, new_hints.len()); if Self::first_timestamp_newer(&new_timestamp, &guard.0) {
if last_updated_timestamp < new_timestamp { guard.0 = new_timestamp;
let mut guard = self.hints.write(); guard.1 = new_hints;
match self.last_updated_timestamp.compare_exchange( }
last_updated_timestamp, }
new_timestamp,
atomic::Ordering::AcqRel, fn first_timestamp_newer(
atomic::Ordering::Acquire, first: &HashMap<usize, Global>,
) { second: &HashMap<usize, Global>,
Ok(_) => *guard = new_hints, ) -> bool {
Err(other_value) => { if first.is_empty() {
if other_value < new_timestamp { false
self.last_updated_timestamp } else if second.is_empty() {
.store(new_timestamp, atomic::Ordering::Release); true
*guard = new_hints; } else {
let mut first_newer = false;
let mut second_has_extra_buffers = false;
for (buffer_id, first_version) in first {
match second.get(buffer_id) {
None => {
second_has_extra_buffers = true;
}
Some(second_version) => {
if second_version.changed_since(&first_version) {
return false;
} else if first_version.changed_since(&second_version) {
first_newer = true;
}
} }
} }
} }
first_newer || !second_has_extra_buffers
} }
} }
} }
@ -1340,7 +1348,7 @@ impl Editor {
project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| { project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| {
match event { match event {
project::Event::ReloadInlayHints => { project::Event::ReloadInlayHints => {
editor.update_inlay_hints(cx); editor.try_update_inlay_hints(cx);
} }
_ => {} _ => {}
}; };
@ -1930,7 +1938,7 @@ impl Editor {
s.set_pending(pending, mode); s.set_pending(pending, mode);
}); });
} else { } else {
log::error!("update_selection dispatched with no pending selection"); error!("update_selection dispatched with no pending selection");
return; return;
} }
@ -2634,43 +2642,64 @@ impl Editor {
} }
} }
fn update_inlay_hints(&self, cx: &mut ViewContext<Self>) { fn try_update_inlay_hints(&self, cx: &mut ViewContext<Self>) {
if self.mode != EditorMode::Full { if self.mode != EditorMode::Full {
return; return;
} }
let position = self.selections.newest_anchor().head();
let Some((buffer, _)) = self
.buffer
.read(cx)
.text_anchor_for_position(position.clone(), cx) else { return };
let generator_buffer = buffer.clone(); let mut hint_fetch_tasks = Vec::new();
let new_timestamp = self.buffer().read(cx).all_buffers().into_iter().fold(
HashMap::default(),
|mut buffer_versions, new_buffer| {
let new_buffer_version = new_buffer.read(cx).version();
match buffer_versions.entry(new_buffer.id()) {
hash_map::Entry::Occupied(mut entry) => {
let entry_version = entry.get();
if new_buffer_version.changed_since(&entry_version) {
entry.insert(new_buffer_version);
}
}
hash_map::Entry::Vacant(v) => {
v.insert(new_buffer_version);
}
}
hint_fetch_tasks.push(cx.spawn(|editor, mut cx| async move {
let task = editor
.update(&mut cx, |editor, cx| {
editor.project.as_ref().map(|project| {
project.update(cx, |project, cx| {
let end = new_buffer.read(cx).len();
project.inlay_hints(new_buffer, 0..end, cx)
})
})
})
.context("inlay hints fecth task spawn")?;
match task {
Some(task) => Ok(task.await.context("inlay hints fetch task await")?),
None => anyhow::Ok(Vec::new()),
}
}));
buffer_versions
},
);
let inlay_hints_storage = Arc::clone(&self.inlay_hints); let inlay_hints_storage = Arc::clone(&self.inlay_hints);
// TODO kb should this come from external things like transaction counter instead? if inlay_hints_storage.is_newer(&new_timestamp) {
// This way we can reuse tasks result for the same timestamp? The counter has to be global among all buffer changes & other reloads. cx.spawn(|_, _| async move {
let new_timestamp = self.inlay_hints.new_timestamp(); let mut new_hints = Vec::new();
for task_result in futures::future::join_all(hint_fetch_tasks).await {
// TODO kb waiting before the server starts and handling workspace/inlayHint/refresh commands is kind of orthogonal? match task_result {
// need to be able to not to start new tasks, if current one is running on the same state already. Ok(task_hints) => new_hints.extend(task_hints),
cx.spawn(|editor, mut cx| async move { Err(e) => error!("Failed to update hints for buffer: {e:#}"),
let task = editor.update(&mut cx, |editor, cx| { }
editor.project.as_ref().map(|project| { }
project.update(cx, |project, cx| {
let end = generator_buffer.read(cx).len();
project.inlay_hints(generator_buffer, 0..end, cx)
})
})
})?;
if let Some(task) = task {
// TODO kb contexts everywhere
let new_hints = task.await?;
inlay_hints_storage.update_if_newer(new_hints, new_timestamp); inlay_hints_storage.update_if_newer(new_hints, new_timestamp);
} })
.detach();
anyhow::Ok(()) }
})
.detach_and_log_err(cx);
} }
fn trigger_on_type_formatting( fn trigger_on_type_formatting(
@ -6737,7 +6766,7 @@ impl Editor {
if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) { if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) {
*end_selections = Some(self.selections.disjoint_anchors()); *end_selections = Some(self.selections.disjoint_anchors());
} else { } else {
log::error!("unexpectedly ended a transaction that wasn't started by this editor"); error!("unexpectedly ended a transaction that wasn't started by this editor");
} }
cx.emit(Event::Edited); cx.emit(Event::Edited);
@ -7254,7 +7283,7 @@ impl Editor {
}; };
if update_inlay_hints { if update_inlay_hints {
self.update_inlay_hints(cx); self.try_update_inlay_hints(cx);
} }
} }