diff --git a/Cargo.lock b/Cargo.lock index e1a99c19c2..9f24b3d82c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4039,6 +4039,7 @@ dependencies = [ "http_client", "language", "log", + "lsp-types", "node_runtime", "parking_lot", "paths", @@ -4073,6 +4074,7 @@ dependencies = [ "dap", "gpui", "language", + "lsp-types", "paths", "serde", "serde_json", @@ -8363,7 +8365,7 @@ dependencies = [ [[package]] name = "lsp-types" version = "0.95.1" -source = "git+https://github.com/zed-industries/lsp-types?rev=1fff0dd12e2071c5667327394cfec163d2a466ab#1fff0dd12e2071c5667327394cfec163d2a466ab" +source = "git+https://github.com/zed-industries/lsp-types?rev=c9c189f1c5dd53c624a419ce35bc77ad6a908d18#c9c189f1c5dd53c624a419ce35bc77ad6a908d18" dependencies = [ "bitflags 1.3.2", "serde", diff --git a/Cargo.toml b/Cargo.toml index 4dcd268965..ba9fc3673d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -296,6 +296,7 @@ livekit_api = { path = "crates/livekit_api" } livekit_client = { path = "crates/livekit_client" } lmstudio = { path = "crates/lmstudio" } lsp = { path = "crates/lsp" } +lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "c9c189f1c5dd53c624a419ce35bc77ad6a908d18" } markdown = { path = "crates/markdown" } markdown_preview = { path = "crates/markdown_preview" } media = { path = "crates/media" } diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 719b8643f2..ec66e593d5 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -1544,6 +1544,7 @@ async fn test_mutual_editor_inlay_hint_cache_update( store.update_user_settings::(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + show_value_hints: true, edit_debounce_ms: 0, scroll_debounce_ms: 0, show_type_hints: true, @@ -1559,6 +1560,7 @@ async fn test_mutual_editor_inlay_hint_cache_update( SettingsStore::update_global(cx, |store, cx| { store.update_user_settings::(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: true, edit_debounce_ms: 0, scroll_debounce_ms: 0, @@ -1778,6 +1780,7 @@ async fn test_inlay_hint_refresh_is_forwarded( SettingsStore::update_global(cx, |store, cx| { store.update_user_settings::(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: false, edit_debounce_ms: 0, scroll_debounce_ms: 0, @@ -1794,6 +1797,7 @@ async fn test_inlay_hint_refresh_is_forwarded( SettingsStore::update_global(cx, |store, cx| { store.update_user_settings::(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: true, edit_debounce_ms: 0, scroll_debounce_ms: 0, diff --git a/crates/dap/Cargo.toml b/crates/dap/Cargo.toml index 531276e708..c5e7fec0c1 100644 --- a/crates/dap/Cargo.toml +++ b/crates/dap/Cargo.toml @@ -36,6 +36,7 @@ gpui.workspace = true http_client.workspace = true language.workspace = true log.workspace = true +lsp-types.workspace = true node_runtime.workspace = true parking_lot.workspace = true paths.workspace = true diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index 7b4294dccd..1b927d0051 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -284,6 +284,10 @@ pub async fn fetch_latest_adapter_version_from_github( }) } +pub trait InlineValueProvider { + fn provide(&self, variables: Vec<(String, lsp_types::Range)>) -> Vec; +} + #[async_trait(?Send)] pub trait DebugAdapter: 'static + Send + Sync { fn name(&self) -> DebugAdapterName; @@ -373,7 +377,12 @@ pub trait DebugAdapter: 'static + Send + Sync { user_installed_path: Option, cx: &mut AsyncApp, ) -> Result; + + fn inline_value_provider(&self) -> Option> { + None + } } + #[cfg(any(test, feature = "test-support"))] pub struct FakeAdapter {} diff --git a/crates/dap_adapters/Cargo.toml b/crates/dap_adapters/Cargo.toml index 0a11724aa2..ba461c3e68 100644 --- a/crates/dap_adapters/Cargo.toml +++ b/crates/dap_adapters/Cargo.toml @@ -26,6 +26,7 @@ async-trait.workspace = true dap.workspace = true gpui.workspace = true language.workspace = true +lsp-types.workspace = true paths.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/dap_adapters/src/codelldb.rs b/crates/dap_adapters/src/codelldb.rs index e1b34e2d73..e6e27b25b6 100644 --- a/crates/dap_adapters/src/codelldb.rs +++ b/crates/dap_adapters/src/codelldb.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, path::PathBuf, sync::OnceLock}; use anyhow::{Result, bail}; use async_trait::async_trait; -use dap::adapters::latest_github_release; +use dap::adapters::{InlineValueProvider, latest_github_release}; use gpui::AsyncApp; use task::{DebugRequest, DebugTaskDefinition}; @@ -150,4 +150,25 @@ impl DebugAdapter for CodeLldbDebugAdapter { connection: None, }) } + + fn inline_value_provider(&self) -> Option> { + Some(Box::new(CodeLldbInlineValueProvider)) + } +} + +struct CodeLldbInlineValueProvider; + +impl InlineValueProvider for CodeLldbInlineValueProvider { + fn provide(&self, variables: Vec<(String, lsp_types::Range)>) -> Vec { + variables + .into_iter() + .map(|(variable, range)| { + lsp_types::InlineValue::VariableLookup(lsp_types::InlineValueVariableLookup { + range, + variable_name: Some(variable), + case_sensitive_lookup: true, + }) + }) + .collect() + } } diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index 3c3f587ecc..cde4df9109 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -1,5 +1,5 @@ use crate::*; -use dap::{DebugRequest, StartDebuggingRequestArguments}; +use dap::{StartDebuggingRequestArguments, adapters::InlineValueProvider}; use gpui::AsyncApp; use std::{collections::HashMap, ffi::OsStr, path::PathBuf}; use task::DebugTaskDefinition; @@ -160,4 +160,34 @@ impl DebugAdapter for PythonDebugAdapter { request_args: self.request_args(config), }) } + + fn inline_value_provider(&self) -> Option> { + Some(Box::new(PythonInlineValueProvider)) + } +} + +struct PythonInlineValueProvider; + +impl InlineValueProvider for PythonInlineValueProvider { + fn provide(&self, variables: Vec<(String, lsp_types::Range)>) -> Vec { + variables + .into_iter() + .map(|(variable, range)| { + if variable.contains(".") || variable.contains("[") { + lsp_types::InlineValue::EvaluatableExpression( + lsp_types::InlineValueEvaluatableExpression { + range, + expression: Some(variable), + }, + ) + } else { + lsp_types::InlineValue::VariableLookup(lsp_types::InlineValueVariableLookup { + range, + variable_name: Some(variable), + case_sensitive_lookup: true, + }) + } + }) + .collect() + } } diff --git a/crates/debugger_ui/src/debugger_ui.rs b/crates/debugger_ui/src/debugger_ui.rs index ce3300de5f..1630e27d23 100644 --- a/crates/debugger_ui/src/debugger_ui.rs +++ b/crates/debugger_ui/src/debugger_ui.rs @@ -247,7 +247,7 @@ pub fn init(cx: &mut App) { let stack_id = state.selected_stack_frame_id(cx); state.session().update(cx, |session, cx| { - session.evaluate(text, None, stack_id, None, cx); + session.evaluate(text, None, stack_id, None, cx).detach(); }); }); Some(()) diff --git a/crates/debugger_ui/src/session/running/console.rs b/crates/debugger_ui/src/session/running/console.rs index ad765496c2..86be2269e4 100644 --- a/crates/debugger_ui/src/session/running/console.rs +++ b/crates/debugger_ui/src/session/running/console.rs @@ -141,14 +141,16 @@ impl Console { expression }); - self.session.update(cx, |state, cx| { - state.evaluate( - expression, - Some(dap::EvaluateArgumentsContext::Variables), - self.stack_frame_list.read(cx).selected_stack_frame_id(), - None, - cx, - ); + self.session.update(cx, |session, cx| { + session + .evaluate( + expression, + Some(dap::EvaluateArgumentsContext::Variables), + self.stack_frame_list.read(cx).selected_stack_frame_id(), + None, + cx, + ) + .detach(); }); } diff --git a/crates/debugger_ui/src/session/running/stack_frame_list.rs b/crates/debugger_ui/src/session/running/stack_frame_list.rs index c9e7a9b11c..b60262f2d7 100644 --- a/crates/debugger_ui/src/session/running/stack_frame_list.rs +++ b/crates/debugger_ui/src/session/running/stack_frame_list.rs @@ -10,6 +10,7 @@ use gpui::{ }; use language::PointUtf16; +use project::debugger::breakpoint_store::ActiveStackFrame; use project::debugger::session::{Session, SessionEvent, StackFrame}; use project::{ProjectItem, ProjectPath}; use ui::{Scrollbar, ScrollbarState, Tooltip, prelude::*}; @@ -265,6 +266,7 @@ impl StackFrameList { return Task::ready(Err(anyhow!("Project path not found"))); }; + let stack_frame_id = stack_frame.id; cx.spawn_in(window, async move |this, cx| { let (worktree, relative_path) = this .update(cx, |this, cx| { @@ -313,12 +315,22 @@ impl StackFrameList { .await?; this.update(cx, |this, cx| { + let Some(thread_id) = this.state.read_with(cx, |state, _| state.thread_id)? else { + return Err(anyhow!("No selected thread ID found")); + }; + this.workspace.update(cx, |workspace, cx| { let breakpoint_store = workspace.project().read(cx).breakpoint_store(); breakpoint_store.update(cx, |store, cx| { store.set_active_position( - (this.session.read(cx).session_id(), abs_path, position), + ActiveStackFrame { + session_id: this.session.read(cx).session_id(), + thread_id, + stack_frame_id, + path: abs_path, + position, + }, cx, ); }) diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index 9726740f17..d79461641d 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -419,6 +419,7 @@ actions!( OpenGitBlameCommit, ToggleIndentGuides, ToggleInlayHints, + ToggleInlineValues, ToggleInlineDiagnostics, ToggleEditPrediction, ToggleLineNumbers, diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 29a0dcd273..ec3bc4865c 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -64,6 +64,14 @@ impl Inlay { text: text.into(), } } + + pub fn debugger_hint>(id: usize, position: Anchor, text: T) -> Self { + Self { + id: InlayId::DebuggerValue(id), + position, + text: text.into(), + } + } } impl sum_tree::Item for Transform { @@ -287,6 +295,7 @@ impl<'a> Iterator for InlayChunks<'a> { }) } InlayId::Hint(_) => self.highlight_styles.inlay_hint, + InlayId::DebuggerValue(_) => self.highlight_styles.inlay_hint, }; let next_inlay_highlight_endpoint; let offset_in_inlay = self.output_offset - self.transforms.start().0; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3a98b5950d..aae7a6a859 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -121,8 +121,11 @@ use mouse_context_menu::MouseContextMenu; use persistence::DB; use project::{ ProjectPath, - debugger::breakpoint_store::{ - BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent, + debugger::{ + breakpoint_store::{ + BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent, + }, + session::{Session, SessionEvent}, }, }; @@ -248,10 +251,27 @@ const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers { function: false, }; +struct InlineValueCache { + enabled: bool, + inlays: Vec, + refresh_task: Task>, +} + +impl InlineValueCache { + fn new(enabled: bool) -> Self { + Self { + enabled, + inlays: Vec::new(), + refresh_task: Task::ready(None), + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum InlayId { InlineCompletion(usize), Hint(usize), + DebuggerValue(usize), } impl InlayId { @@ -259,6 +279,7 @@ impl InlayId { match self { Self::InlineCompletion(id) => *id, Self::Hint(id) => *id, + Self::DebuggerValue(id) => *id, } } } @@ -923,6 +944,7 @@ pub struct Editor { mouse_cursor_hidden: bool, hide_mouse_mode: HideMouseMode, pub change_list: ChangeList, + inline_value_cache: InlineValueCache, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] @@ -1517,6 +1539,8 @@ impl Editor { if editor.go_to_active_debug_line(window, cx) { cx.stop_propagation(); } + + editor.refresh_inline_values(cx); } _ => {} }, @@ -1659,6 +1683,7 @@ impl Editor { released_too_fast: false, }, inline_diagnostics_enabled: mode.is_full(), + inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints), inlay_hint_cache: InlayHintCache::new(inlay_hint_settings), gutter_hovered: false, @@ -1788,6 +1813,33 @@ impl Editor { }, )); + if let Some(dap_store) = this + .project + .as_ref() + .map(|project| project.read(cx).dap_store()) + { + let weak_editor = cx.weak_entity(); + + this._subscriptions + .push( + cx.observe_new::(move |_, _, cx| { + let session_entity = cx.entity(); + weak_editor + .update(cx, |editor, cx| { + editor._subscriptions.push( + cx.subscribe(&session_entity, Self::on_debug_session_event), + ); + }) + .ok(); + }), + ); + + for session in dap_store.read(cx).sessions().cloned().collect::>() { + this._subscriptions + .push(cx.subscribe(&session, Self::on_debug_session_event)); + } + } + this.end_selection(window, cx); this.scroll_manager.show_scrollbars(window, cx); jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx); @@ -4195,6 +4247,17 @@ impl Editor { } } + pub fn toggle_inline_values( + &mut self, + _: &ToggleInlineValues, + _: &mut Window, + cx: &mut Context, + ) { + self.inline_value_cache.enabled = !self.inline_value_cache.enabled; + + self.refresh_inline_values(cx); + } + pub fn toggle_inlay_hints( &mut self, _: &ToggleInlayHints, @@ -4211,6 +4274,10 @@ impl Editor { self.inlay_hint_cache.enabled } + pub fn inline_values_enabled(&self) -> bool { + self.inline_value_cache.enabled + } + fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context) { if self.semantics_provider.is_none() || !self.mode.is_full() { return; @@ -16343,34 +16410,33 @@ impl Editor { maybe!({ let breakpoint_store = self.breakpoint_store.as_ref()?; - let Some((_, _, active_position)) = - breakpoint_store.read(cx).active_position().cloned() + let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned() else { self.clear_row_highlights::(); return None; }; + let position = active_stack_frame.position; + let buffer_id = position.buffer_id?; let snapshot = self .project .as_ref()? .read(cx) - .buffer_for_id(active_position.buffer_id?, cx)? + .buffer_for_id(buffer_id, cx)? .read(cx) .snapshot(); let mut handled = false; - for (id, ExcerptRange { context, .. }) in self - .buffer - .read(cx) - .excerpts_for_buffer(active_position.buffer_id?, cx) + for (id, ExcerptRange { context, .. }) in + self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx) { - if context.start.cmp(&active_position, &snapshot).is_ge() - || context.end.cmp(&active_position, &snapshot).is_lt() + if context.start.cmp(&position, &snapshot).is_ge() + || context.end.cmp(&position, &snapshot).is_lt() { continue; } let snapshot = self.buffer.read(cx).snapshot(cx); - let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?; + let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?; handled = true; self.clear_row_highlights::(); @@ -16383,6 +16449,7 @@ impl Editor { cx.notify(); } + handled.then_some(()) }) .is_some() @@ -17374,6 +17441,87 @@ impl Editor { cx.notify(); } + fn on_debug_session_event( + &mut self, + _session: Entity, + event: &SessionEvent, + cx: &mut Context, + ) { + match event { + SessionEvent::InvalidateInlineValue => { + self.refresh_inline_values(cx); + } + _ => {} + } + } + + fn refresh_inline_values(&mut self, cx: &mut Context) { + let Some(project) = self.project.clone() else { + return; + }; + let Some(buffer) = self.buffer.read(cx).as_singleton() else { + return; + }; + if !self.inline_value_cache.enabled { + let inlays = std::mem::take(&mut self.inline_value_cache.inlays); + self.splice_inlays(&inlays, Vec::new(), cx); + return; + } + + let current_execution_position = self + .highlighted_rows + .get(&TypeId::of::()) + .and_then(|lines| lines.last().map(|line| line.range.start)); + + self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| { + let snapshot = editor + .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx)) + .ok()?; + + let inline_values = editor + .update(cx, |_, cx| { + let Some(current_execution_position) = current_execution_position else { + return Some(Task::ready(Ok(Vec::new()))); + }; + + // todo(debugger) when introducing multi buffer inline values check execution position's buffer id to make sure the text + // anchor is in the same buffer + let range = + buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor; + project.inline_values(buffer, range, cx) + }) + .ok() + .flatten()? + .await + .context("refreshing debugger inlays") + .log_err()?; + + let (excerpt_id, buffer_id) = snapshot + .excerpts() + .next() + .map(|excerpt| (excerpt.0, excerpt.1.remote_id()))?; + editor + .update(cx, |editor, cx| { + let new_inlays = inline_values + .into_iter() + .map(|debugger_value| { + Inlay::debugger_hint( + post_inc(&mut editor.next_inlay_id), + Anchor::in_buffer(excerpt_id, buffer_id, debugger_value.position), + debugger_value.text(), + ) + }) + .collect::>(); + let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect(); + std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids); + + editor.splice_inlays(&inlay_ids, new_inlays, cx); + }) + .ok()?; + Some(()) + }); + } + fn on_buffer_event( &mut self, multibuffer: &Entity, @@ -18909,6 +19057,13 @@ pub trait SemanticsProvider { cx: &mut App, ) -> Option>>; + fn inline_values( + &self, + buffer_handle: Entity, + range: Range, + cx: &mut App, + ) -> Option>>>; + fn inlay_hints( &self, buffer_handle: Entity, @@ -19366,13 +19521,33 @@ impl SemanticsProvider for Entity { fn supports_inlay_hints(&self, buffer: &Entity, cx: &mut App) -> bool { // TODO: make this work for remote projects - self.update(cx, |this, cx| { + self.update(cx, |project, cx| { + if project + .active_debug_session(cx) + .is_some_and(|(session, _)| session.read(cx).any_stopped_thread()) + { + return true; + } + buffer.update(cx, |buffer, cx| { - this.any_language_server_supports_inlay_hints(buffer, cx) + project.any_language_server_supports_inlay_hints(buffer, cx) }) }) } + fn inline_values( + &self, + buffer_handle: Entity, + range: Range, + cx: &mut App, + ) -> Option>>> { + self.update(cx, |project, cx| { + let (session, active_stack_frame) = project.active_debug_session(cx)?; + + Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx)) + }) + } + fn inlay_hints( &self, buffer_handle: Entity, diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index fbab3fd8ff..bb3b12fb89 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -1280,6 +1280,7 @@ mod tests { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + show_value_hints: false, edit_debounce_ms: 0, scroll_debounce_ms: 0, show_type_hints: true, diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 3ab6c95d5e..e86070a205 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1614,6 +1614,7 @@ mod tests { async fn test_hover_inlay_label_parts(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: true, edit_debounce_ms: 0, scroll_debounce_ms: 0, diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 1e22487500..b24bd7944a 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -989,6 +989,7 @@ fn fetch_and_update_hints( } let buffer = editor.buffer().read(cx).buffer(query.buffer_id)?; + if !editor.registered_buffers.contains_key(&query.buffer_id) { if let Some(project) = editor.project.as_ref() { project.update(cx, |project, cx| { @@ -999,6 +1000,7 @@ fn fetch_and_update_hints( }) } } + editor .semantics_provider .as_ref()? @@ -1324,6 +1326,7 @@ pub mod tests { let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: true, edit_debounce_ms: 0, scroll_debounce_ms: 0, @@ -1430,6 +1433,7 @@ pub mod tests { async fn test_cache_update_on_lsp_completion_tasks(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: true, edit_debounce_ms: 0, scroll_debounce_ms: 0, @@ -1535,6 +1539,7 @@ pub mod tests { async fn test_no_hint_updates_for_unrelated_language_files(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: true, edit_debounce_ms: 0, scroll_debounce_ms: 0, @@ -1760,6 +1765,7 @@ pub mod tests { let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: true, edit_debounce_ms: 0, scroll_debounce_ms: 0, @@ -1919,6 +1925,7 @@ pub mod tests { ] { update_test_language_settings(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: true, edit_debounce_ms: 0, scroll_debounce_ms: 0, @@ -1962,6 +1969,7 @@ pub mod tests { let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]); update_test_language_settings(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: false, edit_debounce_ms: 0, scroll_debounce_ms: 0, @@ -2017,6 +2025,7 @@ pub mod tests { let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]); update_test_language_settings(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: true, edit_debounce_ms: 0, scroll_debounce_ms: 0, @@ -2090,6 +2099,7 @@ pub mod tests { async fn test_hint_request_cancellation(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: true, edit_debounce_ms: 0, scroll_debounce_ms: 0, @@ -2222,6 +2232,7 @@ pub mod tests { async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: true, edit_debounce_ms: 0, scroll_debounce_ms: 0, @@ -2521,6 +2532,7 @@ pub mod tests { async fn test_multiple_excerpts_large_multibuffer(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: true, edit_debounce_ms: 0, scroll_debounce_ms: 0, @@ -2829,6 +2841,7 @@ pub mod tests { async fn test_excerpts_removed(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: true, edit_debounce_ms: 0, scroll_debounce_ms: 0, @@ -3005,6 +3018,7 @@ pub mod tests { update_test_language_settings(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: true, edit_debounce_ms: 0, scroll_debounce_ms: 0, @@ -3037,6 +3051,7 @@ pub mod tests { async fn test_inside_char_boundary_range_hints(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: true, edit_debounce_ms: 0, scroll_debounce_ms: 0, @@ -3129,6 +3144,7 @@ pub mod tests { async fn test_toggle_inlay_hints(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: false, edit_debounce_ms: 0, scroll_debounce_ms: 0, @@ -3205,6 +3221,7 @@ pub mod tests { update_test_language_settings(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: true, edit_debounce_ms: 0, scroll_debounce_ms: 0, @@ -3265,6 +3282,7 @@ pub mod tests { async fn test_inlays_at_the_same_place(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { + show_value_hints: true, enabled: true, edit_debounce_ms: 0, scroll_debounce_ms: 0, diff --git a/crates/editor/src/proposed_changes_editor.rs b/crates/editor/src/proposed_changes_editor.rs index 90cf6605ed..0eebddb640 100644 --- a/crates/editor/src/proposed_changes_editor.rs +++ b/crates/editor/src/proposed_changes_editor.rs @@ -455,6 +455,15 @@ impl SemanticsProvider for BranchBufferSemanticsProvider { self.0.inlay_hints(buffer, range, cx) } + fn inline_values( + &self, + _: Entity, + _: Range, + _: &mut App, + ) -> Option>>> { + None + } + fn resolve_inlay_hint( &self, hint: project::InlayHint, diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 5097a0bab0..aac6df01b2 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1,12 +1,6 @@ -pub use crate::{ - Grammar, Language, LanguageRegistry, - diagnostic_set::DiagnosticSet, - highlight_map::{HighlightId, HighlightMap}, - proto, -}; use crate::{ - LanguageScope, Outline, OutlineConfig, RunnableCapture, RunnableTag, TextObject, - TreeSitterOptions, + DebugVariableCapture, LanguageScope, Outline, OutlineConfig, RunnableCapture, RunnableTag, + TextObject, TreeSitterOptions, diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, language_settings::{LanguageSettings, language_settings}, outline::OutlineItem, @@ -17,6 +11,12 @@ use crate::{ task_context::RunnableRange, text_diff::text_diff, }; +pub use crate::{ + Grammar, Language, LanguageRegistry, + diagnostic_set::DiagnosticSet, + highlight_map::{HighlightId, HighlightMap}, + proto, +}; use anyhow::{Context as _, Result, anyhow}; use async_watch as watch; use clock::Lamport; @@ -73,6 +73,12 @@ pub use {tree_sitter_rust, tree_sitter_typescript}; pub use lsp::DiagnosticSeverity; +#[derive(Debug)] +pub struct DebugVariableRanges { + pub buffer_id: BufferId, + pub range: Range, +} + /// A label for the background task spawned by the buffer to compute /// a diff against the contents of its file. pub static BUFFER_DIFF_TASK: LazyLock = LazyLock::new(TaskLabel::new); @@ -3888,6 +3894,79 @@ impl BufferSnapshot { }) } + pub fn debug_variable_ranges( + &self, + offset_range: Range, + ) -> impl Iterator + '_ { + let mut syntax_matches = self.syntax.matches(offset_range, self, |grammar| { + grammar + .debug_variables_config + .as_ref() + .map(|config| &config.query) + }); + + let configs = syntax_matches + .grammars() + .iter() + .map(|grammar| grammar.debug_variables_config.as_ref()) + .collect::>(); + + iter::from_fn(move || { + loop { + let mat = syntax_matches.peek()?; + + let variable_ranges = configs[mat.grammar_index].and_then(|config| { + let full_range = mat.captures.iter().fold( + Range { + start: usize::MAX, + end: 0, + }, + |mut acc, next| { + let byte_range = next.node.byte_range(); + if acc.start > byte_range.start { + acc.start = byte_range.start; + } + if acc.end < byte_range.end { + acc.end = byte_range.end; + } + acc + }, + ); + if full_range.start > full_range.end { + // We did not find a full spanning range of this match. + return None; + } + + let captures = mat.captures.iter().filter_map(|capture| { + Some(( + capture, + config.captures.get(capture.index as usize).cloned()?, + )) + }); + + let mut variable_range = None; + for (query, capture) in captures { + if let DebugVariableCapture::Variable = capture { + let _ = variable_range.insert(query.node.byte_range()); + } + } + + Some(DebugVariableRanges { + buffer_id: self.remote_id(), + range: variable_range?, + }) + }); + + syntax_matches.advance(); + if variable_ranges.is_some() { + // It's fine for us to short-circuit on .peek()? returning None. We don't want to return None from this iter if we + // had a capture that did not contain a run marker, hence we'll just loop around for the next capture. + return variable_ranges; + } + } + }) + } + pub fn runnable_ranges( &self, offset_range: Range, diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 85195bfe7a..86e6b8cae9 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -1015,6 +1015,7 @@ pub struct Grammar { pub(crate) brackets_config: Option, pub(crate) redactions_config: Option, pub(crate) runnable_config: Option, + pub(crate) debug_variables_config: Option, pub(crate) indents_config: Option, pub outline_config: Option, pub text_object_config: Option, @@ -1115,6 +1116,18 @@ struct RunnableConfig { pub extra_captures: Vec, } +#[derive(Clone, Debug, PartialEq)] +enum DebugVariableCapture { + Named(SharedString), + Variable, +} + +#[derive(Debug)] +struct DebugVariablesConfig { + pub query: Query, + pub captures: Vec, +} + struct OverrideConfig { query: Query, values: HashMap, @@ -1175,6 +1188,7 @@ impl Language { override_config: None, redactions_config: None, runnable_config: None, + debug_variables_config: None, error_query: Query::new(&ts_language, "(ERROR) @error").ok(), ts_language, highlight_map: Default::default(), @@ -1246,6 +1260,11 @@ impl Language { .with_text_object_query(query.as_ref()) .context("Error loading textobject query")?; } + if let Some(query) = queries.debug_variables { + self = self + .with_debug_variables_query(query.as_ref()) + .context("Error loading debug variable query")?; + } Ok(self) } @@ -1341,6 +1360,25 @@ impl Language { Ok(self) } + pub fn with_debug_variables_query(mut self, source: &str) -> Result { + let grammar = self + .grammar_mut() + .ok_or_else(|| anyhow!("cannot mutate grammar"))?; + let query = Query::new(&grammar.ts_language, source)?; + + let mut captures = Vec::new(); + for name in query.capture_names() { + captures.push(if *name == "debug_variable" { + DebugVariableCapture::Variable + } else { + DebugVariableCapture::Named(name.to_string().into()) + }); + } + grammar.debug_variables_config = Some(DebugVariablesConfig { query, captures }); + + Ok(self) + } + pub fn with_embedding_query(mut self, source: &str) -> Result { let grammar = self .grammar_mut() diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index 7ba3f3b0ae..d87c6db5dd 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -214,6 +214,7 @@ pub const QUERY_FILENAME_PREFIXES: &[( ("overrides", |q| &mut q.overrides), ("redactions", |q| &mut q.redactions), ("runnables", |q| &mut q.runnables), + ("debug_variables", |q| &mut q.debug_variables), ("textobjects", |q| &mut q.text_objects), ]; @@ -230,6 +231,7 @@ pub struct LanguageQueries { pub redactions: Option>, pub runnables: Option>, pub text_objects: Option>, + pub debug_variables: Option>, } #[derive(Clone, Default)] diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 96610846bd..ce7019bd85 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -971,6 +971,11 @@ pub struct InlayHintSettings { /// Default: false #[serde(default)] pub enabled: bool, + /// Global switch to toggle inline values on and off. + /// + /// Default: false + #[serde(default)] + pub show_value_hints: bool, /// Whether type hints should be shown. /// /// Default: true diff --git a/crates/languages/src/python/debug_variables.scm b/crates/languages/src/python/debug_variables.scm new file mode 100644 index 0000000000..a434e07bf0 --- /dev/null +++ b/crates/languages/src/python/debug_variables.scm @@ -0,0 +1,5 @@ +(assignment + left: (identifier) @debug_variable) + +(function_definition + parameters: (parameters (identifier) @debug_variable)) diff --git a/crates/languages/src/rust/debug_variables.scm b/crates/languages/src/rust/debug_variables.scm new file mode 100644 index 0000000000..9ef51035ef --- /dev/null +++ b/crates/languages/src/rust/debug_variables.scm @@ -0,0 +1,3 @@ +(let_declaration pattern: (identifier) @debug_variable) + +(parameter (identifier) @debug_variable) diff --git a/crates/lsp/Cargo.toml b/crates/lsp/Cargo.toml index d661e4c159..715049c59d 100644 --- a/crates/lsp/Cargo.toml +++ b/crates/lsp/Cargo.toml @@ -22,7 +22,7 @@ collections.workspace = true futures.workspace = true gpui.workspace = true log.workspace = true -lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "1fff0dd12e2071c5667327394cfec163d2a466ab" } +lsp-types.workspace = true parking_lot.workspace = true postage.workspace = true serde.workspace = true diff --git a/crates/project/src/debugger/breakpoint_store.rs b/crates/project/src/debugger/breakpoint_store.rs index ee0511ca01..c47b3ea8fa 100644 --- a/crates/project/src/debugger/breakpoint_store.rs +++ b/crates/project/src/debugger/breakpoint_store.rs @@ -4,7 +4,7 @@ use anyhow::{Result, anyhow}; use breakpoints_in_file::BreakpointsInFile; use collections::BTreeMap; -use dap::client::SessionId; +use dap::{StackFrameId, client::SessionId}; use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, Subscription, Task}; use itertools::Itertools; use language::{Buffer, BufferSnapshot, proto::serialize_anchor as serialize_text_anchor}; @@ -17,6 +17,8 @@ use text::{Point, PointUtf16}; use crate::{Project, ProjectPath, buffer_store::BufferStore, worktree_store::WorktreeStore}; +use super::session::ThreadId; + mod breakpoints_in_file { use language::{BufferEvent, DiskState}; @@ -108,10 +110,20 @@ enum BreakpointStoreMode { Local(LocalBreakpointStore), Remote(RemoteBreakpointStore), } + +#[derive(Clone)] +pub struct ActiveStackFrame { + pub session_id: SessionId, + pub thread_id: ThreadId, + pub stack_frame_id: StackFrameId, + pub path: Arc, + pub position: text::Anchor, +} + pub struct BreakpointStore { breakpoints: BTreeMap, BreakpointsInFile>, downstream_client: Option<(AnyProtoClient, u64)>, - active_stack_frame: Option<(SessionId, Arc, text::Anchor)>, + active_stack_frame: Option, // E.g ssh mode: BreakpointStoreMode, } @@ -493,7 +505,7 @@ impl BreakpointStore { }) } - pub fn active_position(&self) -> Option<&(SessionId, Arc, text::Anchor)> { + pub fn active_position(&self) -> Option<&ActiveStackFrame> { self.active_stack_frame.as_ref() } @@ -504,7 +516,7 @@ impl BreakpointStore { ) { if let Some(session_id) = session_id { self.active_stack_frame - .take_if(|(id, _, _)| *id == session_id); + .take_if(|active_stack_frame| active_stack_frame.session_id == session_id); } else { self.active_stack_frame.take(); } @@ -513,11 +525,7 @@ impl BreakpointStore { cx.notify(); } - pub fn set_active_position( - &mut self, - position: (SessionId, Arc, text::Anchor), - cx: &mut Context, - ) { + pub fn set_active_position(&mut self, position: ActiveStackFrame, cx: &mut Context) { self.active_stack_frame = Some(position); cx.emit(BreakpointStoreEvent::ActiveDebugLineChanged); cx.notify(); diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index f5fddb75d9..c0bf4bb1dd 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -4,7 +4,7 @@ use super::{ session::{self, Session, SessionStateEvent}, }; use crate::{ - ProjectEnvironment, + InlayHint, InlayHintLabel, ProjectEnvironment, ResolveState, project_settings::ProjectSettings, terminals::{SshCommand, wrap_for_ssh}, worktree_store::WorktreeStore, @@ -15,7 +15,7 @@ use collections::HashMap; use dap::{ Capabilities, CompletionItem, CompletionsArguments, DapRegistry, EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments, Source, - StartDebuggingRequestArguments, + StackFrameId, StartDebuggingRequestArguments, adapters::{DapStatus, DebugAdapterBinary, DebugAdapterName, TcpArguments}, client::SessionId, messages::Message, @@ -28,7 +28,10 @@ use futures::{ }; use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task}; use http_client::HttpClient; -use language::{BinaryStatus, LanguageRegistry, LanguageToolchainStore}; +use language::{ + BinaryStatus, Buffer, LanguageRegistry, LanguageToolchainStore, + language_settings::InlayHintKind, range_from_lsp, +}; use lsp::LanguageServerName; use node_runtime::NodeRuntime; @@ -763,6 +766,103 @@ impl DapStore { }) } + pub fn resolve_inline_values( + &self, + session: Entity, + stack_frame_id: StackFrameId, + buffer_handle: Entity, + inline_values: Vec, + cx: &mut Context, + ) -> Task>> { + let snapshot = buffer_handle.read(cx).snapshot(); + let all_variables = session.read(cx).variables_by_stack_frame_id(stack_frame_id); + + cx.spawn(async move |_, cx| { + let mut inlay_hints = Vec::with_capacity(inline_values.len()); + for inline_value in inline_values.iter() { + match inline_value { + lsp::InlineValue::Text(text) => { + inlay_hints.push(InlayHint { + position: snapshot.anchor_after(range_from_lsp(text.range).end), + label: InlayHintLabel::String(format!(": {}", text.text)), + kind: Some(InlayHintKind::Type), + padding_left: false, + padding_right: false, + tooltip: None, + resolve_state: ResolveState::Resolved, + }); + } + lsp::InlineValue::VariableLookup(variable_lookup) => { + let range = range_from_lsp(variable_lookup.range); + + let mut variable_name = variable_lookup + .variable_name + .clone() + .unwrap_or_else(|| snapshot.text_for_range(range.clone()).collect()); + + if !variable_lookup.case_sensitive_lookup { + variable_name = variable_name.to_ascii_lowercase(); + } + + let Some(variable) = all_variables.iter().find(|variable| { + if variable_lookup.case_sensitive_lookup { + variable.name == variable_name + } else { + variable.name.to_ascii_lowercase() == variable_name + } + }) else { + continue; + }; + + inlay_hints.push(InlayHint { + position: snapshot.anchor_after(range.end), + label: InlayHintLabel::String(format!(": {}", variable.value)), + kind: Some(InlayHintKind::Type), + padding_left: false, + padding_right: false, + tooltip: None, + resolve_state: ResolveState::Resolved, + }); + } + lsp::InlineValue::EvaluatableExpression(expression) => { + let range = range_from_lsp(expression.range); + + let expression = expression + .expression + .clone() + .unwrap_or_else(|| snapshot.text_for_range(range.clone()).collect()); + + let Ok(eval_task) = session.update(cx, |session, cx| { + session.evaluate( + expression, + Some(EvaluateArgumentsContext::Variables), + Some(stack_frame_id), + None, + cx, + ) + }) else { + continue; + }; + + if let Some(response) = eval_task.await { + inlay_hints.push(InlayHint { + position: snapshot.anchor_after(range.end), + label: InlayHintLabel::String(format!(": {}", response.result)), + kind: Some(InlayHintKind::Type), + padding_left: false, + padding_right: false, + tooltip: None, + resolve_state: ResolveState::Resolved, + }); + }; + } + }; + } + + Ok(inlay_hints) + }) + } + pub fn shutdown_sessions(&mut self, cx: &mut Context) -> Task<()> { let mut tasks = vec![]; for session_id in self.sessions.keys().cloned().collect::>() { diff --git a/crates/project/src/debugger/session.rs b/crates/project/src/debugger/session.rs index 6a147bbb4e..dde153d9b0 100644 --- a/crates/project/src/debugger/session.rs +++ b/crates/project/src/debugger/session.rs @@ -20,7 +20,9 @@ use dap::{ client::{DebugAdapterClient, SessionId}, messages::{Events, Message}, }; -use dap::{ExceptionBreakpointsFilter, ExceptionFilterOptions, OutputEventCategory}; +use dap::{ + EvaluateResponse, ExceptionBreakpointsFilter, ExceptionFilterOptions, OutputEventCategory, +}; use futures::channel::oneshot; use futures::{FutureExt, future::Shared}; use gpui::{ @@ -649,6 +651,7 @@ pub enum SessionEvent { StackTrace, Variables, Threads, + InvalidateInlineValue, CapabilitiesLoaded, } @@ -1060,6 +1063,7 @@ impl Session { .map(Into::into) .filter(|_| !event.preserve_focus_hint.unwrap_or(false)), )); + cx.emit(SessionEvent::InvalidateInlineValue); cx.notify(); } @@ -1281,6 +1285,10 @@ impl Session { }); } + pub fn any_stopped_thread(&self) -> bool { + self.thread_states.any_stopped_thread() + } + pub fn thread_status(&self, thread_id: ThreadId) -> ThreadStatus { self.thread_states.thread_status(thread_id) } @@ -1802,6 +1810,20 @@ impl Session { .unwrap_or_default() } + pub fn variables_by_stack_frame_id(&self, stack_frame_id: StackFrameId) -> Vec { + let Some(stack_frame) = self.stack_frames.get(&stack_frame_id) else { + return Vec::new(); + }; + + stack_frame + .scopes + .iter() + .filter_map(|scope| self.variables.get(&scope.variables_reference)) + .flatten() + .cloned() + .collect() + } + pub fn variables( &mut self, variables_reference: VariableReference, @@ -1867,7 +1889,7 @@ impl Session { frame_id: Option, source: Option, cx: &mut Context, - ) { + ) -> Task> { self.request( EvaluateCommand { expression, @@ -1896,7 +1918,6 @@ impl Session { }, cx, ) - .detach(); } pub fn location( @@ -1915,6 +1936,7 @@ impl Session { ); self.locations.get(&reference).cloned() } + pub fn disconnect_client(&mut self, cx: &mut Context) { let command = DisconnectCommand { restart: Some(false), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 3c4b9e9658..1d950ed651 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -41,13 +41,14 @@ use client::{ }; use clock::ReplicaId; -use dap::client::DebugAdapterClient; +use dap::{DapRegistry, client::DebugAdapterClient}; use collections::{BTreeSet, HashMap, HashSet}; use debounced_delay::DebouncedDelay; use debugger::{ - breakpoint_store::BreakpointStore, + breakpoint_store::{ActiveStackFrame, BreakpointStore}, dap_store::{DapStore, DapStoreEvent}, + session::Session, }; pub use environment::ProjectEnvironment; #[cfg(test)] @@ -63,7 +64,7 @@ use image_store::{ImageItemEvent, ImageStoreEvent}; use ::git::{blame::Blame, status::FileStatus}; use gpui::{ AnyEntity, App, AppContext, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter, Hsla, - SharedString, Task, WeakEntity, Window, + SharedString, Task, WeakEntity, Window, prelude::FluentBuilder, }; use itertools::Itertools; use language::{ @@ -1551,6 +1552,15 @@ impl Project { self.breakpoint_store.clone() } + pub fn active_debug_session(&self, cx: &App) -> Option<(Entity, ActiveStackFrame)> { + let active_position = self.breakpoint_store.read(cx).active_position()?; + let session = self + .dap_store + .read(cx) + .session_by_id(active_position.session_id)?; + Some((session, active_position.clone())) + } + pub fn lsp_store(&self) -> Entity { self.lsp_store.clone() } @@ -3484,6 +3494,69 @@ impl Project { }) } + pub fn inline_values( + &mut self, + session: Entity, + active_stack_frame: ActiveStackFrame, + buffer_handle: Entity, + range: Range, + cx: &mut Context, + ) -> Task>> { + let snapshot = buffer_handle.read(cx).snapshot(); + + let Some(inline_value_provider) = session + .read(cx) + .adapter_name() + .map(|adapter_name| DapRegistry::global(cx).adapter(&adapter_name)) + .and_then(|adapter| adapter.inline_value_provider()) + else { + return Task::ready(Err(anyhow::anyhow!("Inline value provider not found"))); + }; + + let mut text_objects = + snapshot.text_object_ranges(range.end..range.end, Default::default()); + let text_object_range = text_objects + .find(|(_, obj)| matches!(obj, language::TextObject::AroundFunction)) + .map(|(range, _)| snapshot.anchor_before(range.start)) + .unwrap_or(range.start); + + let variable_ranges = snapshot + .debug_variable_ranges( + text_object_range.to_offset(&snapshot)..range.end.to_offset(&snapshot), + ) + .filter_map(|range| { + let lsp_range = language::range_to_lsp( + range.range.start.to_point_utf16(&snapshot) + ..range.range.end.to_point_utf16(&snapshot), + ) + .ok()?; + + Some(( + snapshot.text_for_range(range.range).collect::(), + lsp_range, + )) + }) + .collect::>(); + + let inline_values = inline_value_provider.provide(variable_ranges); + + let stack_frame_id = active_stack_frame.stack_frame_id; + cx.spawn(async move |this, cx| { + this.update(cx, |project, cx| { + project.dap_store().update(cx, |dap_store, cx| { + dap_store.resolve_inline_values( + session, + stack_frame_id, + buffer_handle, + inline_values, + cx, + ) + }) + })? + .await + }) + } + pub fn inlay_hints( &mut self, buffer_handle: Entity, diff --git a/crates/zed/src/zed/quick_action_bar.rs b/crates/zed/src/zed/quick_action_bar.rs index 6f8b6efa70..8a89b1ecc7 100644 --- a/crates/zed/src/zed/quick_action_bar.rs +++ b/crates/zed/src/zed/quick_action_bar.rs @@ -90,6 +90,7 @@ impl Render for QuickActionBar { let editor_value = editor.read(cx); let selection_menu_enabled = editor_value.selection_menu_enabled(cx); let inlay_hints_enabled = editor_value.inlay_hints_enabled(); + let inline_values_enabled = editor_value.inline_values_enabled(); let inline_diagnostics_enabled = editor_value.show_inline_diagnostics(); let supports_inline_diagnostics = editor_value.inline_diagnostics_enabled(); let git_blame_inline_enabled = editor_value.git_blame_inline_enabled(); @@ -224,6 +225,28 @@ impl Render for QuickActionBar { } }, ); + + menu = menu.toggleable_entry( + "Inline Values", + inline_values_enabled, + IconPosition::Start, + Some(editor::actions::ToggleInlineValues.boxed_clone()), + { + let editor = editor.clone(); + move |window, cx| { + editor + .update(cx, |editor, cx| { + editor.toggle_inline_values( + &editor::actions::ToggleInlineValues, + window, + cx, + ); + }) + .ok(); + } + } + ); + } if supports_inline_diagnostics {