diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 038b62f499..a583bdee0e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -70,7 +70,10 @@ pub use multi_buffer::{ }; use multi_buffer::{MultiBufferChunks, ToOffsetUtf16}; use ordered_float::OrderedFloat; -use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction}; +use parking_lot::RwLock; +use project::{ + FormatTrigger, InlayHint, Location, LocationLink, Project, ProjectPath, ProjectTransaction, +}; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, }; @@ -87,7 +90,10 @@ use std::{ num::NonZeroU32, ops::{Deref, DerefMut, Range}, path::Path, - sync::Arc, + sync::{ + atomic::{self, AtomicUsize}, + Arc, + }, time::{Duration, Instant}, }; pub use sum_tree::Bias; @@ -535,6 +541,7 @@ pub struct Editor { gutter_hovered: bool, link_go_to_definition_state: LinkGoToDefinitionState, copilot_state: CopilotState, + inlay_hints: Arc, _subscriptions: Vec, } @@ -1151,6 +1158,47 @@ impl CopilotState { } } +#[derive(Debug, Default)] +struct InlayHintState { + hints: RwLock>, + last_updated_timestamp: AtomicUsize, + hints_generation: AtomicUsize, +} + +impl InlayHintState { + pub fn new_timestamp(&self) -> usize { + self.hints_generation + .fetch_add(1, atomic::Ordering::Release) + + 1 + } + + pub fn read(&self) -> Vec { + self.hints.read().clone() + } + + pub fn update_if_newer(&self, new_hints: Vec, new_timestamp: usize) { + let last_updated_timestamp = self.last_updated_timestamp.load(atomic::Ordering::Acquire); + if last_updated_timestamp < new_timestamp { + let mut guard = self.hints.write(); + match self.last_updated_timestamp.compare_exchange( + last_updated_timestamp, + new_timestamp, + atomic::Ordering::AcqRel, + atomic::Ordering::Acquire, + ) { + Ok(_) => *guard = new_hints, + Err(other_value) => { + if other_value < new_timestamp { + self.last_updated_timestamp + .store(new_timestamp, atomic::Ordering::Release); + *guard = new_hints; + } + } + } + } + } +} + #[derive(Debug)] struct ActiveDiagnosticGroup { primary_range: Range, @@ -1340,6 +1388,7 @@ impl Editor { hover_state: Default::default(), link_go_to_definition_state: Default::default(), copilot_state: Default::default(), + inlay_hints: Arc::new(InlayHintState::default()), gutter_hovered: false, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), @@ -1366,6 +1415,8 @@ impl Editor { } this.report_editor_event("open", None, cx); + // this.update_inlay_hints(cx); + this } @@ -2151,10 +2202,6 @@ impl Editor { } } - if let Some(hints_task) = this.request_inlay_hints(cx) { - hints_task.detach_and_log_err(cx); - } - if had_active_copilot_suggestion { this.refresh_copilot_suggestions(true, cx); if !this.has_active_copilot_suggestion(cx) { @@ -2581,25 +2628,45 @@ impl Editor { } } - // TODO kb proper inlay hints handling - fn request_inlay_hints(&self, cx: &mut ViewContext) -> Option>> { - let project = self.project.as_ref()?; + fn update_inlay_hints(&self, cx: &mut ViewContext) { + if self.mode != EditorMode::Full { + return; + } let position = self.selections.newest_anchor().head(); - let (buffer, _) = self + let Some((buffer, _)) = self .buffer .read(cx) - .text_anchor_for_position(position.clone(), cx)?; + .text_anchor_for_position(position.clone(), cx) else { return }; - let end = buffer.read(cx).len(); - let inlay_hints_task = project.update(cx, |project, cx| { - project.inlay_hints(buffer.clone(), 0..end, cx) - }); + let generator_buffer = buffer.clone(); + let inlay_hints_storage = Arc::clone(&self.inlay_hints); + // TODO kb should this come from external things like transaction counter instead? + // This way we can reuse tasks result for the same timestamp? The counter has to be global among all buffer changes & other reloads. + let new_timestamp = self.inlay_hints.new_timestamp(); - Some(cx.spawn(|_, _| async move { - let inlay_hints = inlay_hints_task.await?; - dbg!(inlay_hints); - Ok(()) - })) + // TODO kb this would not work until the language server is ready, how to wait for it? + // TODO kb waiting before the server starts and handling workspace/inlayHint/refresh commands is kind of orthogonal? + // need to be able to not to start new tasks, if current one is running on the same state already. + 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| { + // TODO kb use visible_lines as a range instead? + 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); + } + + anyhow::Ok(()) + }) + .detach_and_log_err(cx); } fn trigger_on_type_formatting( @@ -6640,7 +6707,10 @@ impl Editor { ) -> Option { self.start_transaction_at(Instant::now(), cx); update(self, cx); - self.end_transaction_at(Instant::now(), cx) + let transaction_id = self.end_transaction_at(Instant::now(), cx); + // TODO kb is this the right idea? Maybe instead we should react on `BufferEvent::Edited`? + self.update_inlay_hints(cx); + transaction_id } fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext) { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 6525e7fc22..ee0bd3e8b4 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -879,6 +879,7 @@ impl EditorElement { for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() { let row = start_row + ix as u32; line_with_invisibles.draw( + editor, layout, row, scroll_top, @@ -1794,6 +1795,7 @@ impl LineWithInvisibles { fn draw( &self, + editor: &mut Editor, layout: &LayoutState, row: u32, scroll_top: f32, @@ -1817,6 +1819,10 @@ impl LineWithInvisibles { cx, ); + // TODO kb bad: cloning happens very frequently, check the timestamp first + let new_hints = editor.inlay_hints.read(); + // dbg!(new_hints); + self.draw_invisibles( &selection_ranges, layout, diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 95c7dc5fa9..798f35ba5c 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -389,7 +389,7 @@ impl LanguageServer { ..WorkspaceSymbolClientCapabilities::default() }), inlay_hint: Some(InlayHintWorkspaceClientCapabilities { - refresh_support: Default::default(), + refresh_support: Some(true), }), ..Default::default() }), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a7ab9e9068..46da79c5b7 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -325,7 +325,7 @@ pub struct Location { pub range: Range, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct InlayHint { pub position: Anchor, pub label: InlayHintLabel, @@ -333,32 +333,32 @@ pub struct InlayHint { pub tooltip: Option, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum InlayHintLabel { String(String), LabelParts(Vec), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct InlayHintLabelPart { pub value: String, pub tooltip: Option, pub location: Option, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum InlayHintTooltip { String(String), MarkupContent(MarkupContent), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum InlayHintLabelPartTooltip { String(String), MarkupContent(MarkupContent), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct MarkupContent { pub kind: String, pub value: String, @@ -2810,6 +2810,24 @@ impl Project { }) .detach(); + language_server + .on_request::({ + dbg!("!!!!!!!!!!!!!!"); + let this = this.downgrade(); + move |params, cx| async move { + // TODO kb: trigger an event, to call on every open editor + // TODO kb does not get called now, why? + dbg!("@@@@@@@@@@@@@@@@@@@@@@@@@@"); + + let _this = this + .upgrade(&cx) + .ok_or_else(|| anyhow!("project dropped"))?; + dbg!(params); + Ok(()) + } + }) + .detach(); + let disk_based_diagnostics_progress_token = adapter.disk_based_diagnostics_progress_token.clone();