diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 2f7cbb1d240..a38ceefc166 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -5664,6 +5664,19 @@ impl EditorElement { CursorStyle::PointingHand, &layout.position_map.text_hitbox, ); + } else if !window.modifiers().modified() + && let Some(hovered_command) = editor.hovered_inlay_hint_command() + && hovered_command.contains_point( + &layout.position_map.snapshot, + layout + .position_map + .point_for_position(window.mouse_position()), + ) + { + window.set_cursor_style( + CursorStyle::PointingHand, + &layout.position_map.text_hitbox, + ); } else { window.set_cursor_style(CursorStyle::IBeam, &layout.position_map.text_hitbox); }; diff --git a/crates/editor/src/element/mouse.rs b/crates/editor/src/element/mouse.rs index 7db3f77f5bf..c82edb9f02c 100644 --- a/crates/editor/src/element/mouse.rs +++ b/crates/editor/src/element/mouse.rs @@ -613,6 +613,16 @@ impl EditorElement { return; } + if !event.modifiers.modified() + && text_hitbox.is_hovered(window) + && editor.hovered_inlay_hint_command().is_some_and(|command| { + command.contains_point(&position_map.snapshot, point_for_position) + }) + { + cx.stop_propagation(); + return; + } + if EditorSettings::get_global(cx) .drag_and_drop_selection .enabled @@ -943,8 +953,6 @@ impl EditorElement { if let Some(mouse_position) = event.mouse_position() && !pending_nonempty_selections - && hovered_link_modifier - && mouse_down_hovered_link_modifier && text_hitbox.is_hovered(window) && !matches!( editor.selection_drag_state, @@ -952,10 +960,27 @@ impl EditorElement { ) { let point = position_map.point_for_position(mouse_position); - editor.handle_click_hovered_link(point, event.modifiers(), window, cx); - editor.selection_drag_state = SelectionDragState::None; + if let ClickEvent::Mouse(mouse_event) = event + && mouse_event.up.click_count == 1 + && !mouse_event.down.modifiers.modified() + && !mouse_event.up.modifiers.modified() + && editor.activate_hovered_inlay_hint_command( + &position_map.snapshot, + position_map.point_for_position(mouse_event.down.position), + point, + cx, + ) + { + editor.selection_drag_state = SelectionDragState::None; + cx.stop_propagation(); + return; + } - cx.stop_propagation(); + if hovered_link_modifier && mouse_down_hovered_link_modifier { + editor.handle_click_hovered_link(point, event.modifiers(), window, cx); + editor.selection_drag_state = SelectionDragState::None; + cx.stop_propagation(); + } } } diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index b0435bd0dac..5e073791053 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -1071,7 +1071,9 @@ mod tests { test::editor_lsp_test_context::EditorLspTestContext, }; use futures::StreamExt; - use gpui::{Modifiers, MousePressureEvent, PressureStage}; + use gpui::{ + Modifiers, MouseButton, MouseDownEvent, MousePressureEvent, MouseUpEvent, PressureStage, + }; use indoc::indoc; use language::Point; use lsp::request::{GotoDefinition, GotoTypeDefinition}; @@ -1712,7 +1714,11 @@ mod tests { let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { inlay_hint_provider: Some(lsp::OneOf::Left(true)), - ..Default::default() + execute_command_provider: Some(lsp::ExecuteCommandOptions { + commands: vec!["upgrade".to_string()], + ..lsp::ExecuteCommandOptions::default() + }), + ..lsp::ServerCapabilities::default() }, cx, ) @@ -1756,7 +1762,12 @@ mod tests { uri: params.text_document.uri, range: target_range, }), - ..Default::default() + command: Some(lsp::Command { + title: "Upgrade".to_string(), + command: "upgrade".to_string(), + arguments: Some(vec![serde_json::json!("3.5.16")]), + }), + ..lsp::InlayHintLabelPart::default() }]), kind: Some(lsp::InlayHintKind::TYPE), text_edits: None, @@ -1770,6 +1781,13 @@ mod tests { .next() .await; cx.background_executor.run_until_parked(); + let mut command_requests = cx + .lsp + .set_request_handler::(|params, _| async move { + assert_eq!(params.command, "upgrade"); + assert_eq!(params.arguments, vec![serde_json::json!("3.5.16")]); + Ok(Some(serde_json::Value::Null)) + }); cx.update_editor(|editor, _window, cx| { let expected_layers = vec![hint_label.to_string()]; assert_eq!(expected_layers, cached_hint_labels(editor, cx)); @@ -1831,6 +1849,63 @@ mod tests { assert!(actual_ranges.is_empty(), "When no cmd is pressed, should have no hint label selected, but got: {actual_ranges:?}"); }); + cx.update_editor(|editor, _window, cx| { + let hovered_inlay = editor + .display_map + .read(cx) + .current_inlays() + .find(|inlay| inlay.id == InlayId::Hint(0)) + .cloned() + .expect("hovered inlay"); + let hovered_inlay_id = hovered_inlay.id; + editor.splice_inlays(&[hovered_inlay_id], vec![hovered_inlay], cx); + assert!( + editor.hovered_inlay_hint_command().is_none(), + "replacing the hovered inlay should clear its command" + ); + }); + cx.simulate_click(hover_point, Modifiers::none()); + cx.background_executor.run_until_parked(); + assert!( + command_requests.try_recv().is_err(), + "clicking without moving after hint replacement should not run the stale command" + ); + + cx.simulate_mouse_move(hover_point, None, Modifiers::none()); + cx.background_executor.run_until_parked(); + cx.simulate_click(hover_point, Modifiers::none()); + command_requests + .next() + .await + .expect("execute command request"); + cx.background_executor.run_until_parked(); + cx.assert_editor_state(indoc! {" + struct TestStruct; + + fn main() { + let variableˇ = TestStruct; + } + "}); + + cx.simulate_event(MouseDownEvent { + position: hover_point, + modifiers: Modifiers::none(), + button: MouseButton::Left, + click_count: 2, + first_mouse: false, + }); + cx.simulate_event(MouseUpEvent { + position: hover_point, + modifiers: Modifiers::none(), + button: MouseButton::Left, + click_count: 2, + }); + cx.background_executor.run_until_parked(); + assert!( + command_requests.try_recv().is_err(), + "Second click of a double click should not re-run the inlay hint command" + ); + cx.simulate_modifiers_change(Modifiers::secondary_key()); cx.background_executor.run_until_parked(); cx.simulate_click(hover_point, Modifiers::secondary_key()); diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index c7e9761ba8b..f3d0c6c2c33 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -2815,6 +2815,7 @@ mod tests { value: label.to_string(), tooltip: None, location: None, + command: None, }]; let hint_start = InlayOffset(MultiBufferOffset(100)); @@ -2859,11 +2860,13 @@ mod tests { value: "→ ".to_string(), // 4 bytes (3 + 1) tooltip: None, location: None, + command: None, }, InlayHintLabelPart { value: "path".to_string(), // 4 bytes tooltip: None, location: None, + command: None, }, ]; diff --git a/crates/editor/src/inlays.rs b/crates/editor/src/inlays.rs index 689e273ce28..f1e107529c4 100644 --- a/crates/editor/src/inlays.rs +++ b/crates/editor/src/inlays.rs @@ -158,7 +158,7 @@ impl Editor { ) { if let Some(inlay_hints) = &mut self.inlay_hints { for id_to_remove in to_remove { - inlay_hints.added_hints.remove(id_to_remove); + inlay_hints.remove_inlay(id_to_remove); } } self.display_map.update(cx, |display_map, cx| { diff --git a/crates/editor/src/inlays/inlay_hints.rs b/crates/editor/src/inlays/inlay_hints.rs index ee42e68e6cc..d206af52677 100644 --- a/crates/editor/src/inlays/inlay_hints.rs +++ b/crates/editor/src/inlays/inlay_hints.rs @@ -7,7 +7,7 @@ use std::{ use clock::Global; use collections::{HashMap, HashSet}; use futures::future::join_all; -use gpui::{App, Entity, Pixels, Task}; +use gpui::{App, Entity, Pixels, Task, TaskExt}; use itertools::Itertools; use language::{ BufferRow, @@ -16,8 +16,8 @@ use language::{ use lsp::LanguageServerId; use multi_buffer::{Anchor, MultiBufferSnapshot}; use project::{ - HoverBlock, HoverBlockKind, InlayHintLabel, InlayHintLabelPartTooltip, InlayHintTooltip, - InvalidationStrategy, ResolveState, + CodeAction, HoverBlock, HoverBlockKind, InlayHintLabel, InlayHintLabelPartTooltip, + InlayHintTooltip, InvalidationStrategy, LspAction, ResolveState, lsp_store::{CacheInlayHints, ResolvedHint}, }; use text::{Bias, BufferId}; @@ -33,6 +33,34 @@ use crate::{ inlays::InlaySplice, }; +#[derive(Debug)] +pub(crate) struct HoveredInlayHintCommand { + highlight: InlayHighlight, + buffer_id: BufferId, + action: CodeAction, +} + +impl HoveredInlayHintCommand { + pub(crate) fn contains_point( + &self, + snapshot: &EditorSnapshot, + point_for_position: PointForPosition, + ) -> bool { + if point_for_position.column_overshoot_after_line_end != 0 + || point_for_position.as_valid().is_some() + || !snapshot.can_resolve(&self.highlight.inlay_position) + { + return false; + } + let hovered_offset = + snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left); + let hint_start = snapshot.anchor_to_inlay_offset(self.highlight.inlay_position); + let part_range = InlayOffset(hint_start.0 + self.highlight.range.start) + ..InlayOffset(hint_start.0 + self.highlight.range.end); + part_range.contains(&hovered_offset) + } +} + pub fn inlay_hint_settings( location: Anchor, snapshot: &MultiBufferSnapshot, @@ -53,6 +81,7 @@ pub struct LspInlayHintData { hint_chunk_fetching: HashMap>)>, invalidate_hints_for_buffers: HashSet, pub added_hints: HashMap>, + hovered_command: Option, } impl LspInlayHintData { @@ -68,6 +97,7 @@ impl LspInlayHintData { invalidate_debounce: debounce_value(settings.edit_debounce_ms), append_debounce: debounce_value(settings.scroll_debounce_ms), allowed_hint_kinds: settings.enabled_inlay_hint_kinds(), + hovered_command: None, } } @@ -76,12 +106,11 @@ impl LspInlayHintData { return None; } self.modifiers_override = new_override; - if (self.enabled && self.modifiers_override) || (!self.enabled && !self.modifiers_override) - { + if self.should_show() { + Some(true) + } else { self.clear(); Some(false) - } else { - Some(true) } } @@ -101,6 +130,7 @@ impl LspInlayHintData { self.hint_refresh_tasks.clear(); self.hint_chunk_fetching.clear(); self.added_hints.clear(); + self.hovered_command = None; } /// Like `clear`, but only wipes tracking state for the given buffer IDs. @@ -112,6 +142,13 @@ impl LspInlayHintData { current_hints: impl IntoIterator, snapshot: &MultiBufferSnapshot, ) { + if self + .hovered_command + .as_ref() + .is_some_and(|command| buffer_ids.contains(&command.buffer_id)) + { + self.hovered_command = None; + } for buffer_id in buffer_ids { self.hint_refresh_tasks.remove(buffer_id); self.hint_chunk_fetching.remove(buffer_id); @@ -224,6 +261,25 @@ impl LspInlayHintData { self.hint_chunk_fetching.remove(buffer_id); } } + + pub(super) fn remove_inlay(&mut self, inlay_id: &InlayId) { + self.added_hints.remove(inlay_id); + if self + .hovered_command + .as_ref() + .is_some_and(|command| command.highlight.inlay == *inlay_id) + { + self.hovered_command = None; + } + } + + fn should_refresh(&self) -> bool { + self.enabled || self.modifiers_override + } + + fn should_show(&self) -> bool { + self.enabled != self.modifiers_override + } } #[derive(Debug, Clone)] @@ -551,15 +607,12 @@ impl Editor { } }; - match &mut self.inlay_hints { - Some(inlay_hints) => { - if !inlay_hints.enabled - && !matches!(reason, InlayHintRefreshReason::ModifiersChanged(_)) - { - return None; - } - } - None => return None, + if !self + .inlay_hints + .as_ref() + .is_some_and(LspInlayHintData::should_refresh) + { + return None; } Some(invalidate_cache) @@ -595,6 +648,7 @@ impl Editor { }; let mut go_to_definition_updated = false; let mut hover_updated = false; + let mut inlay_command_updated = false; if let Some(hovered_offset) = hovered_offset { let buffer_snapshot = self.buffer().read(cx).snapshot(cx); let previous_valid_anchor = buffer_snapshot.anchor_at( @@ -619,13 +673,12 @@ impl Editor { }) .max_by_key(|hint| hint.id) { - if let Some(ResolvedHint::Resolved(cached_hint)) = buffer_snapshot - .anchor_to_buffer_anchor(hovered_hint.position) - .and_then(|(anchor, _)| { + if let Some((buffer_anchor, _)) = + buffer_snapshot.anchor_to_buffer_anchor(hovered_hint.position) + && let Some(ResolvedHint::Resolved(cached_hint)) = lsp_store.update(cx, |lsp_store, cx| { - lsp_store.resolved_hint(anchor.buffer_id, hovered_hint.id, cx) + lsp_store.resolved_hint(buffer_anchor.buffer_id, hovered_hint.id, cx) }) - }) { match cached_hint.resolve_state { ResolveState::Resolved => { @@ -693,6 +746,24 @@ impl Editor { inlay_position: hovered_hint.position, range: highlight_start..highlight_end, }; + if let Some((server_id, command)) = + hovered_hint_part.command + && let Some(inlay_hints) = self.inlay_hints.as_mut() + { + inlay_command_updated = true; + inlay_hints.hovered_command = + Some(HoveredInlayHintCommand { + highlight: highlight.clone(), + buffer_id: buffer_anchor.buffer_id, + action: CodeAction { + server_id, + range: cached_hint.position + ..cached_hint.position, + lsp_action: LspAction::Command(command), + resolved: true, + }, + }); + } if let Some(tooltip) = hovered_hint_part.tooltip { hover_popover::hover_at_inlay( self, @@ -756,6 +827,40 @@ impl Editor { if !hover_updated { hover_popover::hover_at(self, None, mouse_position, window, cx); } + if !inlay_command_updated && let Some(inlay_hints) = self.inlay_hints.as_mut() { + inlay_hints.hovered_command = None; + } + } + + pub(crate) fn hovered_inlay_hint_command(&self) -> Option<&HoveredInlayHintCommand> { + self.inlay_hints.as_ref()?.hovered_command.as_ref() + } + + pub(crate) fn activate_hovered_inlay_hint_command( + &mut self, + snapshot: &EditorSnapshot, + down: PointForPosition, + up: PointForPosition, + cx: &mut Context, + ) -> bool { + let Some(state) = self.hovered_inlay_hint_command().filter(|state| { + state.contains_point(snapshot, down) && state.contains_point(snapshot, up) + }) else { + return false; + }; + let Some(buffer) = self.buffer().read(cx).buffer(state.buffer_id) else { + return false; + }; + let Some(project) = self.project().cloned() else { + return false; + }; + let action = state.action.clone(); + project + .update(cx, |project, cx| { + project.apply_code_action(buffer, action, true, cx) + }) + .detach_and_log_err(cx); + true } fn inlay_hints_for_buffer( @@ -807,6 +912,7 @@ impl Editor { let Some(inlay_hints) = &mut self.inlay_hints else { return; }; + let should_show = inlay_hints.should_show(); let Some(buffer_snapshot) = self .buffer .read(cx) @@ -901,7 +1007,8 @@ impl Editor { hints_deduplicated }) .filter(|(hint_id, lsp_hint)| { - inlay_hints.allowed_hint_kinds.contains(&lsp_hint.kind) + should_show + && inlay_hints.allowed_hint_kinds.contains(&lsp_hint.kind) && inlay_hints .added_hints .insert(*hint_id, lsp_hint.kind) @@ -1002,7 +1109,9 @@ fn spawn_editor_hints_refresh( #[cfg(test)] pub mod tests { + use super::{HoveredInlayHintCommand, LspInlayHintData}; use crate::editor_tests::update_test_language_settings; + use crate::hover_links::InlayHighlight; use crate::inlays::inlay_hints::InlayHintRefreshReason; use crate::scroll::Autoscroll; use crate::scroll::ScrollAmount; @@ -1012,26 +1121,80 @@ pub mod tests { use futures::{StreamExt, future}; use gpui::{AppContext as _, Context, TestAppContext, WindowHandle}; use itertools::Itertools as _; - use language::language_settings::InlayHintKind; + use language::language_settings::{InlayHintKind, InlayHintSettings}; use language::{Capability, FakeLspAdapter}; use language::{Language, LanguageConfig, LanguageMatcher}; use languages::rust_lang; - use lsp::{DEFAULT_LSP_REQUEST_TIMEOUT, FakeLanguageServer}; + use lsp::{DEFAULT_LSP_REQUEST_TIMEOUT, FakeLanguageServer, LanguageServerId}; use multi_buffer::{MultiBuffer, MultiBufferOffset, PathKey}; use parking_lot::Mutex; use pretty_assertions::assert_eq; - use project::{FakeFs, InvalidationStrategy, Project}; + use project::{CodeAction, FakeFs, InlayId, InvalidationStrategy, LspAction, Project}; use serde_json::json; use settings::{AllLanguageSettingsContent, InlayHintSettingsContent, SettingsStore}; use std::ops::Range; use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}; use std::time::Duration; - use text::{OffsetRangeExt, Point}; + use text::{BufferId, OffsetRangeExt, Point}; use ui::App; use util::path; use util::paths::natural_sort; + #[gpui::test] + fn test_clearing_buffers_clears_only_matching_hovered_command(cx: &mut TestAppContext) { + let hovered_buffer_id = BufferId::new(1).expect("hovered buffer id"); + let other_buffer_id = BufferId::new(2).expect("other buffer id"); + let mut inlay_hints = LspInlayHintData::new(InlayHintSettings { + enabled: true, + show_value_hints: true, + show_type_hints: true, + show_parameter_hints: true, + show_other_hints: true, + show_background: false, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, + toggle_on_modifiers_press: None, + }); + inlay_hints.hovered_command = Some(HoveredInlayHintCommand { + highlight: InlayHighlight { + inlay: InlayId::Hint(1), + inlay_position: multi_buffer::Anchor::Min, + range: 0..1, + }, + buffer_id: hovered_buffer_id, + action: CodeAction { + server_id: LanguageServerId(0), + range: language::Anchor::min_min_range_for_buffer(hovered_buffer_id), + lsp_action: LspAction::Command(lsp::Command { + title: "command".to_string(), + command: "command".to_string(), + arguments: None, + }), + resolved: true, + }, + }); + let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite)); + let snapshot = cx.update(|cx| multi_buffer.read(cx).snapshot(cx)); + + inlay_hints.remove_inlay(&InlayId::Hint(2)); + assert!(inlay_hints.hovered_command.is_some()); + + inlay_hints.clear_for_buffers( + &HashSet::from_iter([other_buffer_id]), + Vec::new(), + &snapshot, + ); + assert!(inlay_hints.hovered_command.is_some()); + + inlay_hints.clear_for_buffers( + &HashSet::from_iter([hovered_buffer_id]), + Vec::new(), + &snapshot, + ); + assert!(inlay_hints.hovered_command.is_none()); + } + #[gpui::test] async fn test_basic_cache_update_with_duplicate_hints(cx: &mut gpui::TestAppContext) { let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); @@ -3741,6 +3904,19 @@ let c = 3;"# ); }) .unwrap(); + + editor + .update(cx, |editor, _, cx| { + editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx); + }) + .unwrap(); + cx.executor().run_until_parked(); + editor + .update(cx, |editor, _, cx| { + assert_eq!(Vec::::new(), visible_hint_labels(editor, cx)); + }) + .unwrap(); + editor .update(cx, |editor, _, cx| { editor.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(true), cx); @@ -3889,6 +4065,34 @@ let c = 3;"# ); }) .unwrap(); + + editor + .update(cx, |editor, window, cx| { + editor.toggle_inlay_hints(&crate::ToggleInlayHints, window, cx); + editor.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(true), cx); + editor.handle_input("x", window, cx); + }) + .unwrap(); + cx.executor().run_until_parked(); + editor + .update(cx, |editor, _, cx| { + assert_eq!(vec!["2".to_string()], cached_hint_labels(editor, cx)); + assert_eq!(Vec::::new(), visible_hint_labels(editor, cx)); + }) + .unwrap(); + + editor + .update(cx, |editor, _, cx| { + editor.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx); + }) + .unwrap(); + cx.executor().run_until_parked(); + editor + .update(cx, |editor, _, cx| { + assert_eq!(vec!["2".to_string()], cached_hint_labels(editor, cx)); + assert_eq!(vec!["2".to_string()], visible_hint_labels(editor, cx)); + }) + .unwrap(); } #[gpui::test] diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 9ce17157a3c..379c7d81ba6 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -3989,6 +3989,7 @@ impl InlayHints { } }), location: Some(server_id).zip(lsp_part.location), + command: Some(server_id).zip(lsp_part.command), }); } InlayHintLabel::LabelParts(parts) @@ -3999,6 +4000,7 @@ impl InlayHints { } pub fn project_to_proto_hint(response_hint: InlayHint) -> proto::InlayHint { + let position = response_hint.position; let (state, lsp_resolve_state) = match response_hint.resolve_state { ResolveState::Resolved => (0, None), ResolveState::CanResolve(server_id, resolve_data) => ( @@ -4046,6 +4048,12 @@ impl InlayHints { location_range_start, location_range_end, language_server_id: label_part.location.as_ref().map(|(server_id, _)| server_id.0 as u64), + command: label_part.command.map(|(server_id, command)| LspStore::serialize_code_action(&CodeAction { + server_id, + range: position..position, + lsp_action: LspAction::Command(command), + resolved: true, + })), }}).collect() }) } @@ -4164,6 +4172,23 @@ impl InlayHints { None => None, } }, + command: match part.command { + Some(command) => { + let action = LspStore::deserialize_code_action(command) + .context("invalid command in inlay hint label part")?; + match action.lsp_action { + LspAction::Command(command) => { + Some((action.server_id, command)) + } + LspAction::Action(_) | LspAction::CodeLens(_) => { + anyhow::bail!( + "unexpected non-command action in inlay hint label part" + ) + } + } + } + None => None, + }, }); } @@ -4249,7 +4274,7 @@ impl InlayHints { }) }), location: part.location.map(|(_, location)| location), - command: None, + command: part.command.map(|(_, command)| command), }) .collect(), ), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 270446bc525..a25101949a8 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -519,7 +519,7 @@ impl InlayId { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq)] pub struct InlayHint { pub position: language::Anchor, pub label: InlayHintLabel, @@ -853,17 +853,18 @@ impl InlayHint { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq)] pub enum InlayHintLabel { String(String), LabelParts(Vec), } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq)] pub struct InlayHintLabelPart { pub value: String, pub tooltip: Option, pub location: Option<(LanguageServerId, lsp::Location)>, + pub command: Option<(LanguageServerId, lsp::Command)>, } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/proto/proto/lsp.proto b/crates/proto/proto/lsp.proto index fd3987e248b..3ebe3c65d00 100644 --- a/crates/proto/proto/lsp.proto +++ b/crates/proto/proto/lsp.proto @@ -572,6 +572,7 @@ message InlayHintLabelPart { PointUtf16 location_range_start = 4; PointUtf16 location_range_end = 5; optional uint64 language_server_id = 6; + optional CodeAction command = 7; } message InlayHintTooltip {