Detailed changes
@@ -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",
@@ -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" }
@@ -1544,6 +1544,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
store.update_user_settings::<AllLanguageSettings>(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::<AllLanguageSettings>(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::<AllLanguageSettings>(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::<AllLanguageSettings>(cx, |settings| {
settings.defaults.inlay_hints = Some(InlayHintSettings {
+ show_value_hints: true,
enabled: true,
edit_debounce_ms: 0,
scroll_debounce_ms: 0,
@@ -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
@@ -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<lsp_types::InlineValue>;
+}
+
#[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<PathBuf>,
cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary>;
+
+ fn inline_value_provider(&self) -> Option<Box<dyn InlineValueProvider>> {
+ None
+ }
}
+
#[cfg(any(test, feature = "test-support"))]
pub struct FakeAdapter {}
@@ -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
@@ -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<Box<dyn InlineValueProvider>> {
+ Some(Box::new(CodeLldbInlineValueProvider))
+ }
+}
+
+struct CodeLldbInlineValueProvider;
+
+impl InlineValueProvider for CodeLldbInlineValueProvider {
+ fn provide(&self, variables: Vec<(String, lsp_types::Range)>) -> Vec<lsp_types::InlineValue> {
+ variables
+ .into_iter()
+ .map(|(variable, range)| {
+ lsp_types::InlineValue::VariableLookup(lsp_types::InlineValueVariableLookup {
+ range,
+ variable_name: Some(variable),
+ case_sensitive_lookup: true,
+ })
+ })
+ .collect()
+ }
}
@@ -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<Box<dyn InlineValueProvider>> {
+ Some(Box::new(PythonInlineValueProvider))
+ }
+}
+
+struct PythonInlineValueProvider;
+
+impl InlineValueProvider for PythonInlineValueProvider {
+ fn provide(&self, variables: Vec<(String, lsp_types::Range)>) -> Vec<lsp_types::InlineValue> {
+ 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()
+ }
}
@@ -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(())
@@ -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();
});
}
@@ -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,
);
})
@@ -419,6 +419,7 @@ actions!(
OpenGitBlameCommit,
ToggleIndentGuides,
ToggleInlayHints,
+ ToggleInlineValues,
ToggleInlineDiagnostics,
ToggleEditPrediction,
ToggleLineNumbers,
@@ -64,6 +64,14 @@ impl Inlay {
text: text.into(),
}
}
+
+ pub fn debugger_hint<T: Into<Rope>>(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;
@@ -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<InlayId>,
+ refresh_task: Task<Option<()>>,
+}
+
+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::<project::debugger::session::Session>(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::<Vec<_>>() {
+ 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>,
+ ) {
+ 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<Self>) {
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::<DebugCurrentRowHighlight>();
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::<DebugCurrentRowHighlight>();
@@ -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<Session>,
+ event: &SessionEvent,
+ cx: &mut Context<Self>,
+ ) {
+ match event {
+ SessionEvent::InvalidateInlineValue => {
+ self.refresh_inline_values(cx);
+ }
+ _ => {}
+ }
+ }
+
+ fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
+ 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::<DebugCurrentRowHighlight>())
+ .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::<Vec<_>>();
+ 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<MultiBuffer>,
@@ -18909,6 +19057,13 @@ pub trait SemanticsProvider {
cx: &mut App,
) -> Option<Task<Vec<project::Hover>>>;
+ fn inline_values(
+ &self,
+ buffer_handle: Entity<Buffer>,
+ range: Range<text::Anchor>,
+ cx: &mut App,
+ ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
+
fn inlay_hints(
&self,
buffer_handle: Entity<Buffer>,
@@ -19366,13 +19521,33 @@ impl SemanticsProvider for Entity<Project> {
fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, 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<Buffer>,
+ range: Range<text::Anchor>,
+ cx: &mut App,
+ ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
+ 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<Buffer>,
@@ -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,
@@ -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,
@@ -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,
@@ -455,6 +455,15 @@ impl SemanticsProvider for BranchBufferSemanticsProvider {
self.0.inlay_hints(buffer, range, cx)
}
+ fn inline_values(
+ &self,
+ _: Entity<Buffer>,
+ _: Range<text::Anchor>,
+ _: &mut App,
+ ) -> Option<Task<anyhow::Result<Vec<project::InlayHint>>>> {
+ None
+ }
+
fn resolve_inlay_hint(
&self,
hint: project::InlayHint,
@@ -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<usize>,
+}
+
/// 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<TaskLabel> = LazyLock::new(TaskLabel::new);
@@ -3888,6 +3894,79 @@ impl BufferSnapshot {
})
}
+ pub fn debug_variable_ranges(
+ &self,
+ offset_range: Range<usize>,
+ ) -> impl Iterator<Item = DebugVariableRanges> + '_ {
+ 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::<Vec<_>>();
+
+ 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<usize>,
@@ -1015,6 +1015,7 @@ pub struct Grammar {
pub(crate) brackets_config: Option<BracketsConfig>,
pub(crate) redactions_config: Option<RedactionConfig>,
pub(crate) runnable_config: Option<RunnableConfig>,
+ pub(crate) debug_variables_config: Option<DebugVariablesConfig>,
pub(crate) indents_config: Option<IndentConfig>,
pub outline_config: Option<OutlineConfig>,
pub text_object_config: Option<TextObjectConfig>,
@@ -1115,6 +1116,18 @@ struct RunnableConfig {
pub extra_captures: Vec<RunnableCapture>,
}
+#[derive(Clone, Debug, PartialEq)]
+enum DebugVariableCapture {
+ Named(SharedString),
+ Variable,
+}
+
+#[derive(Debug)]
+struct DebugVariablesConfig {
+ pub query: Query,
+ pub captures: Vec<DebugVariableCapture>,
+}
+
struct OverrideConfig {
query: Query,
values: HashMap<u32, OverrideEntry>,
@@ -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<Self> {
+ 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<Self> {
let grammar = self
.grammar_mut()
@@ -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<Cow<'static, str>>,
pub runnables: Option<Cow<'static, str>>,
pub text_objects: Option<Cow<'static, str>>,
+ pub debug_variables: Option<Cow<'static, str>>,
}
#[derive(Clone, Default)]
@@ -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
@@ -0,0 +1,5 @@
+(assignment
+ left: (identifier) @debug_variable)
+
+(function_definition
+ parameters: (parameters (identifier) @debug_variable))
@@ -0,0 +1,3 @@
+(let_declaration pattern: (identifier) @debug_variable)
+
+(parameter (identifier) @debug_variable)
@@ -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
@@ -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<Path>,
+ pub position: text::Anchor,
+}
+
pub struct BreakpointStore {
breakpoints: BTreeMap<Arc<Path>, BreakpointsInFile>,
downstream_client: Option<(AnyProtoClient, u64)>,
- active_stack_frame: Option<(SessionId, Arc<Path>, text::Anchor)>,
+ active_stack_frame: Option<ActiveStackFrame>,
// E.g ssh
mode: BreakpointStoreMode,
}
@@ -493,7 +505,7 @@ impl BreakpointStore {
})
}
- pub fn active_position(&self) -> Option<&(SessionId, Arc<Path>, 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<Path>, text::Anchor),
- cx: &mut Context<Self>,
- ) {
+ pub fn set_active_position(&mut self, position: ActiveStackFrame, cx: &mut Context<Self>) {
self.active_stack_frame = Some(position);
cx.emit(BreakpointStoreEvent::ActiveDebugLineChanged);
cx.notify();
@@ -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<Session>,
+ stack_frame_id: StackFrameId,
+ buffer_handle: Entity<Buffer>,
+ inline_values: Vec<lsp::InlineValue>,
+ cx: &mut Context<Self>,
+ ) -> Task<Result<Vec<InlayHint>>> {
+ 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<Self>) -> Task<()> {
let mut tasks = vec![];
for session_id in self.sessions.keys().cloned().collect::<Vec<_>>() {
@@ -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<dap::Variable> {
+ 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<u64>,
source: Option<Source>,
cx: &mut Context<Self>,
- ) {
+ ) -> Task<Option<EvaluateResponse>> {
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<Self>) {
let command = DisconnectCommand {
restart: Some(false),
@@ -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<Session>, 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<LspStore> {
self.lsp_store.clone()
}
@@ -3484,6 +3494,69 @@ impl Project {
})
}
+ pub fn inline_values(
+ &mut self,
+ session: Entity<Session>,
+ active_stack_frame: ActiveStackFrame,
+ buffer_handle: Entity<Buffer>,
+ range: Range<text::Anchor>,
+ cx: &mut Context<Self>,
+ ) -> Task<anyhow::Result<Vec<InlayHint>>> {
+ 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::<String>(),
+ lsp_range,
+ ))
+ })
+ .collect::<Vec<_>>();
+
+ 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<T: ToOffset>(
&mut self,
buffer_handle: Entity<Buffer>,
@@ -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 {