Stub initial hint requests

This commit is contained in:
Kirill Bulatov 2023-05-30 20:36:36 +03:00
parent 8a3b515f56
commit 79b97f9e75
4 changed files with 122 additions and 28 deletions

View file

@ -70,7 +70,10 @@ pub use multi_buffer::{
}; };
use multi_buffer::{MultiBufferChunks, ToOffsetUtf16}; use multi_buffer::{MultiBufferChunks, ToOffsetUtf16};
use ordered_float::OrderedFloat; 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::{ use scroll::{
autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide,
}; };
@ -87,7 +90,10 @@ use std::{
num::NonZeroU32, num::NonZeroU32,
ops::{Deref, DerefMut, Range}, ops::{Deref, DerefMut, Range},
path::Path, path::Path,
sync::Arc, sync::{
atomic::{self, AtomicUsize},
Arc,
},
time::{Duration, Instant}, time::{Duration, Instant},
}; };
pub use sum_tree::Bias; pub use sum_tree::Bias;
@ -535,6 +541,7 @@ pub struct Editor {
gutter_hovered: bool, gutter_hovered: bool,
link_go_to_definition_state: LinkGoToDefinitionState, link_go_to_definition_state: LinkGoToDefinitionState,
copilot_state: CopilotState, copilot_state: CopilotState,
inlay_hints: Arc<InlayHintState>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
@ -1151,6 +1158,47 @@ impl CopilotState {
} }
} }
#[derive(Debug, Default)]
struct InlayHintState {
hints: RwLock<Vec<InlayHint>>,
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<InlayHint> {
self.hints.read().clone()
}
pub fn update_if_newer(&self, new_hints: Vec<InlayHint>, 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)] #[derive(Debug)]
struct ActiveDiagnosticGroup { struct ActiveDiagnosticGroup {
primary_range: Range<Anchor>, primary_range: Range<Anchor>,
@ -1340,6 +1388,7 @@ impl Editor {
hover_state: Default::default(), hover_state: Default::default(),
link_go_to_definition_state: Default::default(), link_go_to_definition_state: Default::default(),
copilot_state: Default::default(), copilot_state: Default::default(),
inlay_hints: Arc::new(InlayHintState::default()),
gutter_hovered: false, gutter_hovered: false,
_subscriptions: vec![ _subscriptions: vec![
cx.observe(&buffer, Self::on_buffer_changed), cx.observe(&buffer, Self::on_buffer_changed),
@ -1366,6 +1415,8 @@ impl Editor {
} }
this.report_editor_event("open", None, cx); this.report_editor_event("open", None, cx);
// this.update_inlay_hints(cx);
this 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 { if had_active_copilot_suggestion {
this.refresh_copilot_suggestions(true, cx); this.refresh_copilot_suggestions(true, cx);
if !this.has_active_copilot_suggestion(cx) { if !this.has_active_copilot_suggestion(cx) {
@ -2581,25 +2628,45 @@ impl Editor {
} }
} }
// TODO kb proper inlay hints handling fn update_inlay_hints(&self, cx: &mut ViewContext<Self>) {
fn request_inlay_hints(&self, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> { if self.mode != EditorMode::Full {
let project = self.project.as_ref()?; return;
}
let position = self.selections.newest_anchor().head(); let position = self.selections.newest_anchor().head();
let (buffer, _) = self let Some((buffer, _)) = self
.buffer .buffer
.read(cx) .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 generator_buffer = buffer.clone();
let inlay_hints_task = project.update(cx, |project, cx| { let inlay_hints_storage = Arc::clone(&self.inlay_hints);
project.inlay_hints(buffer.clone(), 0..end, cx) // 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 { // TODO kb this would not work until the language server is ready, how to wait for it?
let inlay_hints = inlay_hints_task.await?; // TODO kb waiting before the server starts and handling workspace/inlayHint/refresh commands is kind of orthogonal?
dbg!(inlay_hints); // need to be able to not to start new tasks, if current one is running on the same state already.
Ok(()) 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( fn trigger_on_type_formatting(
@ -6640,7 +6707,10 @@ impl Editor {
) -> Option<TransactionId> { ) -> Option<TransactionId> {
self.start_transaction_at(Instant::now(), cx); self.start_transaction_at(Instant::now(), cx);
update(self, 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<Self>) { fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext<Self>) {

View file

@ -879,6 +879,7 @@ impl EditorElement {
for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() { for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
let row = start_row + ix as u32; let row = start_row + ix as u32;
line_with_invisibles.draw( line_with_invisibles.draw(
editor,
layout, layout,
row, row,
scroll_top, scroll_top,
@ -1794,6 +1795,7 @@ impl LineWithInvisibles {
fn draw( fn draw(
&self, &self,
editor: &mut Editor,
layout: &LayoutState, layout: &LayoutState,
row: u32, row: u32,
scroll_top: f32, scroll_top: f32,
@ -1817,6 +1819,10 @@ impl LineWithInvisibles {
cx, 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( self.draw_invisibles(
&selection_ranges, &selection_ranges,
layout, layout,

View file

@ -389,7 +389,7 @@ impl LanguageServer {
..WorkspaceSymbolClientCapabilities::default() ..WorkspaceSymbolClientCapabilities::default()
}), }),
inlay_hint: Some(InlayHintWorkspaceClientCapabilities { inlay_hint: Some(InlayHintWorkspaceClientCapabilities {
refresh_support: Default::default(), refresh_support: Some(true),
}), }),
..Default::default() ..Default::default()
}), }),

View file

@ -325,7 +325,7 @@ pub struct Location {
pub range: Range<language::Anchor>, pub range: Range<language::Anchor>,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct InlayHint { pub struct InlayHint {
pub position: Anchor, pub position: Anchor,
pub label: InlayHintLabel, pub label: InlayHintLabel,
@ -333,32 +333,32 @@ pub struct InlayHint {
pub tooltip: Option<InlayHintTooltip>, pub tooltip: Option<InlayHintTooltip>,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum InlayHintLabel { pub enum InlayHintLabel {
String(String), String(String),
LabelParts(Vec<InlayHintLabelPart>), LabelParts(Vec<InlayHintLabelPart>),
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct InlayHintLabelPart { pub struct InlayHintLabelPart {
pub value: String, pub value: String,
pub tooltip: Option<InlayHintLabelPartTooltip>, pub tooltip: Option<InlayHintLabelPartTooltip>,
pub location: Option<Location>, pub location: Option<Location>,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum InlayHintTooltip { pub enum InlayHintTooltip {
String(String), String(String),
MarkupContent(MarkupContent), MarkupContent(MarkupContent),
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum InlayHintLabelPartTooltip { pub enum InlayHintLabelPartTooltip {
String(String), String(String),
MarkupContent(MarkupContent), MarkupContent(MarkupContent),
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct MarkupContent { pub struct MarkupContent {
pub kind: String, pub kind: String,
pub value: String, pub value: String,
@ -2810,6 +2810,24 @@ impl Project {
}) })
.detach(); .detach();
language_server
.on_request::<lsp::request::InlayHintRefreshRequest, _, _>({
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 = let disk_based_diagnostics_progress_token =
adapter.disk_based_diagnostics_progress_token.clone(); adapter.disk_based_diagnostics_progress_token.clone();