diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index c5ff1f027d..79186d6e8c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -23,7 +23,7 @@ pub mod test; use ::git::diff::DiffHunk; use aho_corasick::AhoCorasick; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context, Result}; use blink_manager::BlinkManager; use client::{ClickhouseEvent, TelemetrySettings}; use clock::{Global, ReplicaId}; @@ -60,21 +60,24 @@ use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, - AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape, - Diagnostic, DiagnosticSeverity, File, IndentKind, IndentSize, Language, OffsetRangeExt, - OffsetUtf16, Point, Selection, SelectionGoal, TransactionId, + point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, + CursorShape, Diagnostic, DiagnosticSeverity, File, IndentKind, IndentSize, Language, + LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal, + TransactionId, }; use link_go_to_definition::{ - hide_link_definition, show_link_definition, DocumentRange, InlayRange, LinkGoToDefinitionState, + hide_link_definition, show_link_definition, DocumentRange, GoToDefinitionLink, InlayRange, + LinkGoToDefinitionState, }; use log::error; +use lsp::LanguageServerId; use multi_buffer::ToOffsetUtf16; pub use multi_buffer::{ Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, }; use ordered_float::OrderedFloat; -use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction}; +use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction}; use rand::{seq::SliceRandom, thread_rng}; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, @@ -6551,7 +6554,14 @@ impl Editor { cx.spawn_labeled("Fetching Definition...", |editor, mut cx| async move { let definitions = definitions.await?; editor.update(&mut cx, |editor, cx| { - editor.navigate_to_definitions(definitions, split, cx); + editor.navigate_to_definitions( + definitions + .into_iter() + .map(GoToDefinitionLink::Text) + .collect(), + split, + cx, + ); })?; Ok::<(), anyhow::Error>(()) }) @@ -6560,7 +6570,7 @@ impl Editor { pub fn navigate_to_definitions( &mut self, - mut definitions: Vec, + mut definitions: Vec, split: bool, cx: &mut ViewContext, ) { @@ -6571,67 +6581,167 @@ impl Editor { // If there is one definition, just open it directly if definitions.len() == 1 { let definition = definitions.pop().unwrap(); - let range = definition - .target - .range - .to_offset(definition.target.buffer.read(cx)); - - let range = self.range_for_match(&range); - if Some(&definition.target.buffer) == self.buffer.read(cx).as_singleton().as_ref() { - self.change_selections(Some(Autoscroll::fit()), cx, |s| { - s.select_ranges([range]); - }); - } else { - cx.window_context().defer(move |cx| { - let target_editor: ViewHandle = workspace.update(cx, |workspace, cx| { - if split { - workspace.split_project_item(definition.target.buffer.clone(), cx) + let target_task = match definition { + GoToDefinitionLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))), + GoToDefinitionLink::InlayHint(lsp_location, server_id) => { + self.compute_target_location(lsp_location, server_id, cx) + } + }; + cx.spawn(|editor, mut cx| async move { + let target = target_task.await.context("target resolution task")?; + if let Some(target) = target { + editor.update(&mut cx, |editor, cx| { + let range = target.range.to_offset(target.buffer.read(cx)); + let range = editor.range_for_match(&range); + if Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref() { + editor.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select_ranges([range]); + }); } else { - workspace.open_project_item(definition.target.buffer.clone(), cx) + cx.window_context().defer(move |cx| { + let target_editor: ViewHandle = + workspace.update(cx, |workspace, cx| { + if split { + workspace.split_project_item(target.buffer.clone(), cx) + } else { + workspace.open_project_item(target.buffer.clone(), cx) + } + }); + target_editor.update(cx, |target_editor, cx| { + // When selecting a definition in a different buffer, disable the nav history + // to avoid creating a history entry at the previous cursor location. + pane.update(cx, |pane, _| pane.disable_history()); + target_editor.change_selections( + Some(Autoscroll::fit()), + cx, + |s| { + s.select_ranges([range]); + }, + ); + pane.update(cx, |pane, _| pane.enable_history()); + }); + }); } - }); - target_editor.update(cx, |target_editor, cx| { - // When selecting a definition in a different buffer, disable the nav history - // to avoid creating a history entry at the previous cursor location. - pane.update(cx, |pane, _| pane.disable_history()); - target_editor.change_selections(Some(Autoscroll::fit()), cx, |s| { - s.select_ranges([range]); - }); - pane.update(cx, |pane, _| pane.enable_history()); - }); - }); - } + }) + } else { + Ok(()) + } + }) + .detach_and_log_err(cx); } else if !definitions.is_empty() { let replica_id = self.replica_id(cx); - cx.window_context().defer(move |cx| { - let title = definitions - .iter() - .find(|definition| definition.origin.is_some()) - .and_then(|definition| { - definition.origin.as_ref().map(|origin| { - let buffer = origin.buffer.read(cx); - format!( - "Definitions for {}", - buffer - .text_for_range(origin.range.clone()) - .collect::() - ) - }) + cx.spawn(|editor, mut cx| async move { + let (title, location_tasks) = editor + .update(&mut cx, |editor, cx| { + let title = definitions + .iter() + .find_map(|definition| match definition { + GoToDefinitionLink::Text(link) => { + link.origin.as_ref().map(|origin| { + let buffer = origin.buffer.read(cx); + format!( + "Definitions for {}", + buffer + .text_for_range(origin.range.clone()) + .collect::() + ) + }) + } + GoToDefinitionLink::InlayHint(_, _) => None, + }) + .unwrap_or("Definitions".to_string()); + let location_tasks = definitions + .into_iter() + .map(|definition| match definition { + GoToDefinitionLink::Text(link) => { + Task::Ready(Some(Ok(Some(link.target)))) + } + GoToDefinitionLink::InlayHint(lsp_location, server_id) => { + editor.compute_target_location(lsp_location, server_id, cx) + } + }) + .collect::>(); + (title, location_tasks) }) - .unwrap_or("Definitions".to_owned()); - let locations = definitions + .context("location tasks preparation")?; + + let locations = futures::future::join_all(location_tasks) + .await .into_iter() - .map(|definition| definition.target) - .collect(); - workspace.update(cx, |workspace, cx| { + .filter_map(|location| location.transpose()) + .collect::>() + .context("location tasks")?; + workspace.update(&mut cx, |workspace, cx| { Self::open_locations_in_multibuffer( workspace, locations, replica_id, title, split, cx, ) }); - }); + + anyhow::Ok(()) + }) + .detach_and_log_err(cx); } } + fn compute_target_location( + &self, + lsp_location: lsp::Location, + server_id: LanguageServerId, + cx: &mut ViewContext, + ) -> Task>> { + let Some(project) = self.project.clone() else { + return Task::Ready(Some(Ok(None))); + }; + + cx.spawn(move |editor, mut cx| async move { + let location_task = editor.update(&mut cx, |editor, cx| { + project.update(cx, |project, cx| { + let language_server_name = + editor.buffer.read(cx).as_singleton().and_then(|buffer| { + project + .language_server_for_buffer(buffer.read(cx), server_id, cx) + .map(|(_, lsp_adapter)| { + LanguageServerName(Arc::from(lsp_adapter.name())) + }) + }); + language_server_name.map(|language_server_name| { + project.open_local_buffer_via_lsp( + lsp_location.uri.clone(), + server_id, + language_server_name, + cx, + ) + }) + }) + })?; + let location = match location_task { + Some(task) => Some({ + let target_buffer_handle = task.await.context("open local buffer")?; + let range = { + target_buffer_handle.update(&mut cx, |target_buffer, _| { + let target_start = target_buffer.clip_point_utf16( + point_from_lsp(lsp_location.range.start), + Bias::Left, + ); + let target_end = target_buffer.clip_point_utf16( + point_from_lsp(lsp_location.range.end), + Bias::Left, + ); + target_buffer.anchor_after(target_start) + ..target_buffer.anchor_before(target_end) + }) + }; + Location { + buffer: target_buffer_handle, + range, + } + }), + None => None, + }; + Ok(location) + }) + } + pub fn find_all_references( workspace: &mut Workspace, _: &FindAllReferences, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 5b52cc3c72..684f92a96a 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -395,9 +395,7 @@ impl EditorElement { update_go_to_definition_link( editor, - point - .map(GoToDefinitionTrigger::Text) - .unwrap_or(GoToDefinitionTrigger::None), + point.map(GoToDefinitionTrigger::Text), cmd, shift, cx, @@ -468,7 +466,7 @@ impl EditorElement { Some(point) => { update_go_to_definition_link( editor, - GoToDefinitionTrigger::Text(point), + Some(GoToDefinitionTrigger::Text(point)), cmd, shift, cx, @@ -487,7 +485,7 @@ impl EditorElement { } } } else { - update_go_to_definition_link(editor, GoToDefinitionTrigger::None, cmd, shift, cx); + update_go_to_definition_link(editor, None, cmd, shift, cx); hover_at(editor, None, cx); } diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 3ce936ae82..89e8d8e246 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -57,19 +57,30 @@ pub struct InlayHover { pub fn find_hovered_hint_part( label_parts: Vec, + padding_left: bool, + padding_right: bool, hint_range: Range, hovered_offset: InlayOffset, ) -> Option<(InlayHintLabelPart, Range)> { if hovered_offset >= hint_range.start && hovered_offset <= hint_range.end { let mut hovered_character = (hovered_offset - hint_range.start).0; let mut part_start = hint_range.start; - for part in label_parts { + let last_label_part_index = label_parts.len() - 1; + for (i, part) in label_parts.into_iter().enumerate() { let part_len = part.value.chars().count(); if hovered_character >= part_len { hovered_character -= part_len; part_start.0 += part_len; } else { - return Some((part, part_start..InlayOffset(part_start.0 + part_len))); + let mut part_end = InlayOffset(part_start.0 + part_len); + if padding_left { + part_start.0 += 1; + part_end.0 += 1; + } + if padding_right && i == last_label_part_index { + part_end.0 -= 1; + } + return Some((part, part_start..part_end)); } } } diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index a4b91e826d..0067c832d3 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/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 sum_tree::Bias; use text::ToOffset; use util::post_inc; @@ -632,8 +633,8 @@ fn determine_query_ranges( return None; } else { vec![ - buffer.anchor_before(excerpt_visible_range.start) - ..buffer.anchor_after(excerpt_visible_range.end), + buffer.anchor_before(snapshot.clip_offset(excerpt_visible_range.start, Bias::Left)) + ..buffer.anchor_after(snapshot.clip_offset(excerpt_visible_range.end, Bias::Right)), ] }; @@ -651,8 +652,8 @@ fn determine_query_ranges( .min(full_excerpt_range_end_offset) .min(buffer.len()); vec![ - buffer.anchor_before(after_visible_range_start) - ..buffer.anchor_after(after_range_end_offset), + buffer.anchor_before(snapshot.clip_offset(after_visible_range_start, Bias::Left)) + ..buffer.anchor_after(snapshot.clip_offset(after_range_end_offset, Bias::Right)), ] }; @@ -668,8 +669,8 @@ fn determine_query_ranges( .saturating_sub(excerpt_visible_len) .max(full_excerpt_range_start_offset); vec![ - buffer.anchor_before(before_range_start_offset) - ..buffer.anchor_after(before_visible_range_end), + buffer.anchor_before(snapshot.clip_offset(before_range_start_offset, Bias::Left)) + ..buffer.anchor_after(snapshot.clip_offset(before_visible_range_end, Bias::Right)), ] }; diff --git a/crates/editor/src/link_go_to_definition.rs b/crates/editor/src/link_go_to_definition.rs index 926c0d6dde..280993dd26 100644 --- a/crates/editor/src/link_go_to_definition.rs +++ b/crates/editor/src/link_go_to_definition.rs @@ -6,9 +6,10 @@ use crate::{ }; use gpui::{Task, ViewContext}; use language::{Bias, ToOffset}; +use lsp::LanguageServerId; use project::{ - HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, Location, - LocationLink, ResolveState, + HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink, + ResolveState, }; use std::ops::Range; use util::TryFutureExt; @@ -18,14 +19,19 @@ pub struct LinkGoToDefinitionState { pub last_trigger_point: Option, pub symbol_range: Option, pub kind: Option, - pub definitions: Vec, + pub definitions: Vec, pub task: Option>>, } pub enum GoToDefinitionTrigger { Text(DisplayPoint), - InlayHint(InlayRange, LocationLink), - None, + InlayHint(InlayRange, lsp::Location, LanguageServerId), +} + +#[derive(Debug, Clone)] +pub enum GoToDefinitionLink { + Text(LocationLink), + InlayHint(lsp::Location, LanguageServerId), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -38,7 +44,7 @@ pub struct InlayRange { #[derive(Debug, Clone)] pub enum TriggerPoint { Text(Anchor), - InlayHint(InlayRange, LocationLink), + InlayHint(InlayRange, lsp::Location, LanguageServerId), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -61,12 +67,12 @@ impl DocumentRange { let point_after_start = range.start.cmp(point, &snapshot.buffer_snapshot).is_le(); point_after_start && range.end.cmp(point, &snapshot.buffer_snapshot).is_ge() } - (DocumentRange::Inlay(range), TriggerPoint::InlayHint(point, _)) => { + (DocumentRange::Inlay(range), TriggerPoint::InlayHint(point, _, _)) => { range.highlight_start.cmp(&point.highlight_end).is_le() && range.highlight_end.cmp(&point.highlight_end).is_ge() } (DocumentRange::Inlay(_), TriggerPoint::Text(_)) - | (DocumentRange::Text(_), TriggerPoint::InlayHint(_, _)) => false, + | (DocumentRange::Text(_), TriggerPoint::InlayHint(_, _, _)) => false, } } } @@ -75,7 +81,7 @@ impl TriggerPoint { fn anchor(&self) -> &Anchor { match self { TriggerPoint::Text(anchor) => anchor, - TriggerPoint::InlayHint(coordinates, _) => &coordinates.inlay_position, + TriggerPoint::InlayHint(coordinates, _, _) => &coordinates.inlay_position, } } @@ -88,14 +94,14 @@ impl TriggerPoint { LinkDefinitionKind::Symbol } } - TriggerPoint::InlayHint(_, _) => LinkDefinitionKind::Type, + TriggerPoint::InlayHint(_, _, _) => LinkDefinitionKind::Type, } } } pub fn update_go_to_definition_link( editor: &mut Editor, - origin: GoToDefinitionTrigger, + origin: Option, cmd_held: bool, shift_held: bool, cx: &mut ViewContext, @@ -105,13 +111,15 @@ pub fn update_go_to_definition_link( // Store new mouse point as an anchor let snapshot = editor.snapshot(cx); let trigger_point = match origin { - GoToDefinitionTrigger::Text(p) => { + Some(GoToDefinitionTrigger::Text(p)) => { Some(TriggerPoint::Text(snapshot.buffer_snapshot.anchor_before( p.to_offset(&snapshot.display_snapshot, Bias::Left), ))) } - GoToDefinitionTrigger::InlayHint(p, target) => Some(TriggerPoint::InlayHint(p, target)), - GoToDefinitionTrigger::None => None, + Some(GoToDefinitionTrigger::InlayHint(p, lsp_location, language_server_id)) => { + Some(TriggerPoint::InlayHint(p, lsp_location, language_server_id)) + } + None => None, }; // If the new point is the same as the previously stored one, return early @@ -210,6 +218,15 @@ pub fn update_inlay_link_and_hover_points( ResolveState::Resolved => { match cached_hint.label { project::InlayHintLabel::String(_) => { + let mut highlight_start = hint_start_offset; + let mut highlight_end = hint_end_offset; + if cached_hint.padding_left { + highlight_start.0 += 1; + highlight_end.0 += 1; + } + if cached_hint.padding_right { + highlight_end.0 -= 1; + } if let Some(tooltip) = cached_hint.tooltip { hover_popover::hover_at_inlay( editor, @@ -230,8 +247,8 @@ pub fn update_inlay_link_and_hover_points( triggered_from: hovered_offset, range: InlayRange { inlay_position: hovered_hint.position, - highlight_start: hint_start_offset, - highlight_end: hint_end_offset, + highlight_start, + highlight_end, }, }, cx, @@ -243,6 +260,8 @@ pub fn update_inlay_link_and_hover_points( if let Some((hovered_hint_part, part_range)) = hover_popover::find_hovered_hint_part( label_parts, + cached_hint.padding_left, + cached_hint.padding_right, hint_start_offset..hint_end_offset, hovered_offset, ) @@ -277,35 +296,25 @@ pub fn update_inlay_link_and_hover_points( ); hover_updated = true; } - if let Some(location) = hovered_hint_part.location { - if let Some(buffer) = - cached_hint.position.buffer_id.and_then(|buffer_id| { - editor.buffer().read(cx).buffer(buffer_id) - }) - { - go_to_definition_updated = true; - update_go_to_definition_link( - editor, - GoToDefinitionTrigger::InlayHint( - InlayRange { - inlay_position: hovered_hint.position, - highlight_start: part_range.start, - highlight_end: part_range.end, - }, - LocationLink { - origin: Some(Location { - buffer, - range: cached_hint.position - ..cached_hint.position, - }), - target: location, - }, - ), - cmd_held, - shift_held, - cx, - ); - } + if let Some((language_server_id, location)) = + hovered_hint_part.location + { + go_to_definition_updated = true; + update_go_to_definition_link( + editor, + Some(GoToDefinitionTrigger::InlayHint( + InlayRange { + inlay_position: hovered_hint.position, + highlight_start: part_range.start, + highlight_end: part_range.end, + }, + location, + language_server_id, + )), + cmd_held, + shift_held, + cx, + ); } } } @@ -317,13 +326,7 @@ pub fn update_inlay_link_and_hover_points( } if !go_to_definition_updated { - update_go_to_definition_link( - editor, - GoToDefinitionTrigger::None, - cmd_held, - shift_held, - cx, - ); + update_go_to_definition_link(editor, None, cmd_held, shift_held, cx); } if !hover_updated { hover_popover::hover_at(editor, None, cx); @@ -415,17 +418,22 @@ pub fn show_link_definition( let end = snapshot .buffer_snapshot .anchor_in_excerpt(excerpt_id.clone(), origin.range.end); - DocumentRange::Text(start..end) }) }), - definition_result, + definition_result + .into_iter() + .map(GoToDefinitionLink::Text) + .collect(), ) }) } - TriggerPoint::InlayHint(trigger_source, trigger_target) => Some(( - Some(DocumentRange::Inlay(trigger_source.clone())), - vec![trigger_target.clone()], + TriggerPoint::InlayHint(trigger_source, lsp_location, server_id) => Some(( + Some(DocumentRange::Inlay(*trigger_source)), + vec![GoToDefinitionLink::InlayHint( + lsp_location.clone(), + *server_id, + )], )), }; @@ -446,43 +454,52 @@ pub fn show_link_definition( // the current location. let any_definition_does_not_contain_current_location = definitions.iter().any(|definition| { - let target = &definition.target; - if target.buffer == buffer { - let range = &target.range; - // Expand range by one character as lsp definition ranges include positions adjacent - // but not contained by the symbol range - let start = buffer_snapshot.clip_offset( - range.start.to_offset(&buffer_snapshot).saturating_sub(1), - Bias::Left, - ); - let end = buffer_snapshot.clip_offset( - range.end.to_offset(&buffer_snapshot) + 1, - Bias::Right, - ); - let offset = buffer_position.to_offset(&buffer_snapshot); - !(start <= offset && end >= offset) - } else { - true + match &definition { + GoToDefinitionLink::Text(link) => { + if link.target.buffer == buffer { + let range = &link.target.range; + // Expand range by one character as lsp definition ranges include positions adjacent + // but not contained by the symbol range + let start = buffer_snapshot.clip_offset( + range + .start + .to_offset(&buffer_snapshot) + .saturating_sub(1), + Bias::Left, + ); + let end = buffer_snapshot.clip_offset( + range.end.to_offset(&buffer_snapshot) + 1, + Bias::Right, + ); + let offset = buffer_position.to_offset(&buffer_snapshot); + !(start <= offset && end >= offset) + } else { + true + } + } + GoToDefinitionLink::InlayHint(_, _) => true, } }); if any_definition_does_not_contain_current_location { // Highlight symbol using theme link definition highlight style let style = theme::current(cx).editor.link_definition; - let highlight_range = symbol_range.unwrap_or_else(|| match trigger_point { - TriggerPoint::Text(trigger_anchor) => { - let snapshot = &snapshot.buffer_snapshot; - // If no symbol range returned from language server, use the surrounding word. - let (offset_range, _) = snapshot.surrounding_word(trigger_anchor); - DocumentRange::Text( - snapshot.anchor_before(offset_range.start) - ..snapshot.anchor_after(offset_range.end), - ) - } - TriggerPoint::InlayHint(inlay_coordinates, _) => { - DocumentRange::Inlay(inlay_coordinates) - } - }); + let highlight_range = + symbol_range.unwrap_or_else(|| match &trigger_point { + TriggerPoint::Text(trigger_anchor) => { + let snapshot = &snapshot.buffer_snapshot; + // If no symbol range returned from language server, use the surrounding word. + let (offset_range, _) = + snapshot.surrounding_word(*trigger_anchor); + DocumentRange::Text( + snapshot.anchor_before(offset_range.start) + ..snapshot.anchor_after(offset_range.end), + ) + } + TriggerPoint::InlayHint(inlay_coordinates, _, _) => { + DocumentRange::Inlay(*inlay_coordinates) + } + }); match highlight_range { DocumentRange::Text(text_range) => this @@ -647,7 +664,7 @@ mod tests { cx.update_editor(|editor, cx| { update_go_to_definition_link( editor, - GoToDefinitionTrigger::Text(hover_point), + Some(GoToDefinitionTrigger::Text(hover_point)), true, true, cx, @@ -759,7 +776,7 @@ mod tests { cx.update_editor(|editor, cx| { update_go_to_definition_link( editor, - GoToDefinitionTrigger::Text(hover_point), + Some(GoToDefinitionTrigger::Text(hover_point)), true, false, cx, @@ -799,7 +816,7 @@ mod tests { cx.update_editor(|editor, cx| { update_go_to_definition_link( editor, - GoToDefinitionTrigger::Text(hover_point), + Some(GoToDefinitionTrigger::Text(hover_point)), true, false, cx, @@ -827,7 +844,7 @@ mod tests { cx.update_editor(|editor, cx| { update_go_to_definition_link( editor, - GoToDefinitionTrigger::Text(hover_point), + Some(GoToDefinitionTrigger::Text(hover_point)), true, false, cx, @@ -850,7 +867,7 @@ mod tests { cx.update_editor(|editor, cx| { update_go_to_definition_link( editor, - GoToDefinitionTrigger::Text(hover_point), + Some(GoToDefinitionTrigger::Text(hover_point)), false, false, cx, @@ -915,7 +932,7 @@ mod tests { cx.update_editor(|editor, cx| { update_go_to_definition_link( editor, - GoToDefinitionTrigger::Text(hover_point), + Some(GoToDefinitionTrigger::Text(hover_point)), true, false, cx, @@ -935,7 +952,7 @@ mod tests { cx.update_editor(|editor, cx| { update_go_to_definition_link( editor, - GoToDefinitionTrigger::Text(hover_point), + Some(GoToDefinitionTrigger::Text(hover_point)), true, false, cx, @@ -958,6 +975,7 @@ mod tests { // the cached location instead Ok(Some(lsp::GotoDefinitionResponse::Link(vec![]))) }); + cx.foreground().run_until_parked(); cx.assert_editor_state(indoc! {" fn «testˇ»() { do_work(); } fn do_work() { test(); } @@ -1037,7 +1055,7 @@ mod tests { cx.update_editor(|editor, cx| { update_go_to_definition_link( editor, - GoToDefinitionTrigger::Text(hover_point), + Some(GoToDefinitionTrigger::Text(hover_point)), true, false, cx, diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 8ca2571869..8239cf8690 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1,6 +1,6 @@ use crate::{ DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintLabel, - InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Item, Location, LocationLink, + InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location, LocationLink, MarkupContent, Project, ProjectTransaction, ResolveState, }; use anyhow::{anyhow, Context, Result}; @@ -14,8 +14,8 @@ use language::{ point_from_lsp, point_to_lsp, proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind, - CodeAction, Completion, LanguageServerName, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, - Transaction, Unclipped, + CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, + Unclipped, }; use lsp::{DocumentHighlightKind, LanguageServer, LanguageServerId, OneOf, ServerCapabilities}; use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc}; @@ -1787,7 +1787,6 @@ impl LspCommand for OnTypeFormatting { impl InlayHints { pub async fn lsp_to_project_hint( lsp_hint: lsp::InlayHint, - project: &ModelHandle, buffer_handle: &ModelHandle, server_id: LanguageServerId, resolve_state: ResolveState, @@ -1809,15 +1808,9 @@ impl InlayHints { buffer.anchor_after(position) } }); - let label = Self::lsp_inlay_label_to_project( - &buffer_handle, - project, - server_id, - lsp_hint.label, - cx, - ) - .await - .context("lsp to project inlay hint conversion")?; + let label = Self::lsp_inlay_label_to_project(lsp_hint.label, server_id) + .await + .context("lsp to project inlay hint conversion")?; let padding_left = if force_no_type_left_padding && kind == Some(InlayHintKind::Type) { false } else { @@ -1847,72 +1840,14 @@ impl InlayHints { } async fn lsp_inlay_label_to_project( - buffer: &ModelHandle, - project: &ModelHandle, - server_id: LanguageServerId, lsp_label: lsp::InlayHintLabel, - cx: &mut AsyncAppContext, + server_id: LanguageServerId, ) -> anyhow::Result { let label = match lsp_label { lsp::InlayHintLabel::String(s) => InlayHintLabel::String(s), lsp::InlayHintLabel::LabelParts(lsp_parts) => { - let mut parts_data = Vec::with_capacity(lsp_parts.len()); - buffer.update(cx, |buffer, cx| { - for lsp_part in lsp_parts { - let location_buffer_task = match &lsp_part.location { - Some(lsp_location) => { - let location_buffer_task = project.update(cx, |project, cx| { - let language_server_name = project - .language_server_for_buffer(buffer, server_id, cx) - .map(|(_, lsp_adapter)| { - LanguageServerName(Arc::from(lsp_adapter.name())) - }); - language_server_name.map(|language_server_name| { - project.open_local_buffer_via_lsp( - lsp_location.uri.clone(), - server_id, - language_server_name, - cx, - ) - }) - }); - Some(lsp_location.clone()).zip(location_buffer_task) - } - None => None, - }; - - parts_data.push((lsp_part, location_buffer_task)); - } - }); - - let mut parts = Vec::with_capacity(parts_data.len()); - for (lsp_part, location_buffer_task) in parts_data { - let location = match location_buffer_task { - Some((lsp_location, target_buffer_handle_task)) => { - let target_buffer_handle = target_buffer_handle_task - .await - .context("resolving location for label part buffer")?; - let range = cx.read(|cx| { - let target_buffer = target_buffer_handle.read(cx); - let target_start = target_buffer.clip_point_utf16( - point_from_lsp(lsp_location.range.start), - Bias::Left, - ); - let target_end = target_buffer.clip_point_utf16( - point_from_lsp(lsp_location.range.end), - Bias::Left, - ); - target_buffer.anchor_after(target_start) - ..target_buffer.anchor_before(target_end) - }); - Some(Location { - buffer: target_buffer_handle, - range, - }) - } - None => None, - }; - + let mut parts = Vec::with_capacity(lsp_parts.len()); + for lsp_part in lsp_parts { parts.push(InlayHintLabelPart { value: lsp_part.value, tooltip: lsp_part.tooltip.map(|tooltip| match tooltip { @@ -1929,7 +1864,7 @@ impl InlayHints { }) } }), - location, + location: Some(server_id).zip(lsp_part.location), }); } InlayHintLabel::LabelParts(parts) @@ -1939,7 +1874,7 @@ impl InlayHints { Ok(label) } - pub fn project_to_proto_hint(response_hint: InlayHint, cx: &AppContext) -> proto::InlayHint { + pub fn project_to_proto_hint(response_hint: InlayHint) -> proto::InlayHint { let (state, lsp_resolve_state) = match response_hint.resolve_state { ResolveState::Resolved => (0, None), ResolveState::CanResolve(server_id, resolve_data) => ( @@ -1969,7 +1904,11 @@ impl InlayHints { InlayHintLabel::String(s) => proto::inlay_hint_label::Label::Value(s), InlayHintLabel::LabelParts(label_parts) => { proto::inlay_hint_label::Label::LabelParts(proto::InlayHintLabelParts { - parts: label_parts.into_iter().map(|label_part| proto::InlayHintLabelPart { + parts: label_parts.into_iter().map(|label_part| { + let location_url = label_part.location.as_ref().map(|(_, location)| location.uri.to_string()); + let location_range_start = label_part.location.as_ref().map(|(_, location)| point_from_lsp(location.range.start).0).map(|point| proto::PointUtf16 { row: point.row, column: point.column }); + let location_range_end = label_part.location.as_ref().map(|(_, location)| point_from_lsp(location.range.end).0).map(|point| proto::PointUtf16 { row: point.row, column: point.column }); + proto::InlayHintLabelPart { value: label_part.value, tooltip: label_part.tooltip.map(|tooltip| { let proto_tooltip = match tooltip { @@ -1981,12 +1920,11 @@ impl InlayHints { }; proto::InlayHintLabelPartTooltip {content: Some(proto_tooltip)} }), - location: label_part.location.map(|location| proto::Location { - start: Some(serialize_anchor(&location.range.start)), - end: Some(serialize_anchor(&location.range.end)), - buffer_id: location.buffer.read(cx).remote_id(), - }), - }).collect() + location_url, + location_range_start, + location_range_end, + language_server_id: label_part.location.as_ref().map(|(server_id, _)| server_id.0 as u64), + }}).collect() }) } }), @@ -1994,16 +1932,12 @@ impl InlayHints { kind: response_hint.kind.map(|kind| kind.name().to_string()), tooltip: response_hint.tooltip.map(|response_tooltip| { let proto_tooltip = match response_tooltip { - InlayHintTooltip::String(s) => { - proto::inlay_hint_tooltip::Content::Value(s) - } + InlayHintTooltip::String(s) => proto::inlay_hint_tooltip::Content::Value(s), InlayHintTooltip::MarkupContent(markup_content) => { - proto::inlay_hint_tooltip::Content::MarkupContent( - proto::MarkupContent { - is_markdown: markup_content.kind == HoverBlockKind::Markdown, - value: markup_content.value, - }, - ) + proto::inlay_hint_tooltip::Content::MarkupContent(proto::MarkupContent { + is_markdown: markup_content.kind == HoverBlockKind::Markdown, + value: markup_content.value, + }) } }; proto::InlayHintTooltip { @@ -2014,16 +1948,7 @@ impl InlayHints { } } - pub async fn proto_to_project_hint( - message_hint: proto::InlayHint, - project: &ModelHandle, - cx: &mut AsyncAppContext, - ) -> anyhow::Result { - let buffer_id = message_hint - .position - .as_ref() - .and_then(|location| location.buffer_id) - .context("missing buffer id")?; + pub fn proto_to_project_hint(message_hint: proto::InlayHint) -> anyhow::Result { let resolve_state = message_hint.resolve_state.as_ref().unwrap_or_else(|| { panic!("incorrect proto inlay hint message: no resolve state in hint {message_hint:?}",) }); @@ -2064,9 +1989,6 @@ impl InlayHints { proto::inlay_hint_label::Label::LabelParts(parts) => { let mut label_parts = Vec::new(); for part in parts.parts { - let buffer = project - .update(cx, |this, cx| this.wait_for_remote_buffer(buffer_id, cx)) - .await?; label_parts.push(InlayHintLabelPart { value: part.value, tooltip: part.tooltip.map(|tooltip| match tooltip.content { @@ -2087,19 +2009,35 @@ impl InlayHints { }), None => InlayHintLabelPartTooltip::String(String::new()), }), - location: match part.location { - Some(location) => Some(Location { - range: location - .start - .and_then(language::proto::deserialize_anchor) - .context("invalid start")? - ..location - .end - .and_then(language::proto::deserialize_anchor) - .context("invalid end")?, - buffer, - }), - None => None, + location: { + match part + .location_url + .zip( + part.location_range_start.and_then(|start| { + Some(start..part.location_range_end?) + }), + ) + .zip(part.language_server_id) + { + Some(((uri, range), server_id)) => Some(( + LanguageServerId(server_id as usize), + lsp::Location { + uri: lsp::Url::parse(&uri) + .context("invalid uri in hint part {part:?}")?, + range: lsp::Range::new( + point_to_lsp(PointUtf16::new( + range.start.row, + range.start.column, + )), + point_to_lsp(PointUtf16::new( + range.end.row, + range.end.column, + )), + ), + }, + )), + None => None, + } }, }); } @@ -2132,12 +2070,7 @@ impl InlayHints { }) } - pub fn project_to_lsp_hint( - hint: InlayHint, - project: &ModelHandle, - snapshot: &BufferSnapshot, - cx: &AsyncAppContext, - ) -> lsp::InlayHint { + pub fn project_to_lsp_hint(hint: InlayHint, snapshot: &BufferSnapshot) -> lsp::InlayHint { lsp::InlayHint { position: point_to_lsp(hint.position.to_point_utf16(snapshot)), kind: hint.kind.map(|kind| match kind { @@ -2190,22 +2123,7 @@ impl InlayHints { } }) }), - location: part.location.and_then(|location| { - let (path, location_snapshot) = cx.read(|cx| { - let buffer = location.buffer.read(cx); - let project_path = buffer.project_path(cx)?; - let location_snapshot = buffer.snapshot(); - let path = project.read(cx).absolute_path(&project_path, cx); - path.zip(Some(location_snapshot)) - })?; - Some(lsp::Location::new( - lsp::Url::from_file_path(path).unwrap(), - range_to_lsp( - location.range.start.to_point_utf16(&location_snapshot) - ..location.range.end.to_point_utf16(&location_snapshot), - ), - )) - }), + location: part.location.map(|(_, location)| location), command: None, }) .collect(), @@ -2299,12 +2217,10 @@ impl LspCommand for InlayHints { ResolveState::Resolved }; - let project = project.clone(); let buffer = buffer.clone(); cx.spawn(|mut cx| async move { InlayHints::lsp_to_project_hint( lsp_hint, - &project, &buffer, server_id, resolve_state, @@ -2359,12 +2275,12 @@ impl LspCommand for InlayHints { _: &mut Project, _: PeerId, buffer_version: &clock::Global, - cx: &mut AppContext, + _: &mut AppContext, ) -> proto::InlayHintsResponse { proto::InlayHintsResponse { hints: response .into_iter() - .map(|response_hint| InlayHints::project_to_proto_hint(response_hint, cx)) + .map(|response_hint| InlayHints::project_to_proto_hint(response_hint)) .collect(), version: serialize_version(buffer_version), } @@ -2373,7 +2289,7 @@ impl LspCommand for InlayHints { async fn response_from_proto( self, message: proto::InlayHintsResponse, - project: ModelHandle, + _: ModelHandle, buffer: ModelHandle, mut cx: AsyncAppContext, ) -> anyhow::Result> { @@ -2385,7 +2301,7 @@ impl LspCommand for InlayHints { let mut hints = Vec::new(); for message_hint in message.hints { - hints.push(InlayHints::proto_to_project_hint(message_hint, &project, &mut cx).await?); + hints.push(InlayHints::proto_to_project_hint(message_hint)?); } Ok(hints) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index bb18a41ad4..547c7dcc95 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -369,7 +369,7 @@ pub enum InlayHintLabel { pub struct InlayHintLabelPart { pub value: String, pub tooltip: Option, - pub location: Option, + pub location: Option<(LanguageServerId, lsp::Location)>, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -1708,7 +1708,7 @@ impl Project { } /// LanguageServerName is owned, because it is inserted into a map - fn open_local_buffer_via_lsp( + pub fn open_local_buffer_via_lsp( &mut self, abs_path: lsp::Url, language_server_id: LanguageServerId, @@ -5069,16 +5069,15 @@ impl Project { } let buffer_snapshot = buffer.snapshot(); - cx.spawn(|project, mut cx| async move { + cx.spawn(|_, mut cx| async move { let resolve_task = lang_server.request::( - InlayHints::project_to_lsp_hint(hint, &project, &buffer_snapshot, &cx), + InlayHints::project_to_lsp_hint(hint, &buffer_snapshot), ); let resolved_hint = resolve_task .await .context("inlay hint resolve LSP request")?; let resolved_hint = InlayHints::lsp_to_project_hint( resolved_hint, - &project, &buffer_handle, server_id, ResolveState::Resolved, @@ -5094,19 +5093,16 @@ impl Project { project_id, buffer_id: buffer_handle.read(cx).remote_id(), language_server_id: server_id.0 as u64, - hint: Some(InlayHints::project_to_proto_hint(hint.clone(), cx)), + hint: Some(InlayHints::project_to_proto_hint(hint.clone())), }; - cx.spawn(|project, mut cx| async move { + cx.spawn(|_, _| async move { let response = client .request(request) .await .context("inlay hints proto request")?; match response.hint { - Some(resolved_hint) => { - InlayHints::proto_to_project_hint(resolved_hint, &project, &mut cx) - .await - .context("inlay hints proto resolve response conversion") - } + Some(resolved_hint) => InlayHints::proto_to_project_hint(resolved_hint) + .context("inlay hints proto resolve response conversion"), None => Ok(hint), } }) @@ -7091,8 +7087,7 @@ impl Project { .payload .hint .expect("incorrect protobuf resolve inlay hint message: missing the inlay hint"); - let hint = InlayHints::proto_to_project_hint(proto_hint, &this, &mut cx) - .await + let hint = InlayHints::proto_to_project_hint(proto_hint) .context("resolved proto inlay hint conversion")?; let buffer = this.update(&mut cx, |this, cx| { this.opened_buffers @@ -7111,10 +7106,8 @@ impl Project { }) .await .context("inlay hints fetch")?; - let resolved_hint = cx.read(|cx| InlayHints::project_to_proto_hint(response_hint, cx)); - Ok(proto::ResolveInlayHintResponse { - hint: Some(resolved_hint), + hint: Some(InlayHints::project_to_proto_hint(response_hint)), }) } @@ -7882,7 +7875,7 @@ impl Project { self.language_servers_for_buffer(buffer, cx).next() } - fn language_server_for_buffer( + pub fn language_server_for_buffer( &self, buffer: &Buffer, server_id: LanguageServerId, diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index ce47830af2..3d855c9c69 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -773,7 +773,10 @@ message InlayHintLabelParts { message InlayHintLabelPart { string value = 1; InlayHintLabelPartTooltip tooltip = 2; - Location location = 3; + optional string location_url = 3; + PointUtf16 location_range_start = 4; + PointUtf16 location_range_end = 5; + optional uint64 language_server_id = 6; } message InlayHintTooltip {