debugger: Copy full variable value instead of trimmed preview (#63902)

# Objective

Fixes #63869 — "Copy Value" in the debugger panel copied the trimmed
display preview instead of the full value.

## Solution

Evaluate the variable's `evaluateName` via the DAP `evaluate` request
using the `clipboard` context (falling back to `variables`), and copy
the returned result. Falls back to the display value if evaluation
fails.

## Testing

- `cargo test -p debugger_ui --lib` passes.

---

Release Notes:

- Fixed debugger "Copy Value" copying the truncated preview instead of
the full value.
This commit is contained in:
Ali authored and GitHub committed 2026-09-08 07:12:28 +00:00
1 parent 6f73c7d0a4
commit 490aad88d5
3 files changed
+110 -6

No files matched your search

@@ -889,13 +889,30 @@ impl VariableList {
return;
};
let variable_value = match &entry.entry {
DapEntry::Variable(dap) => dap.value.clone(),
DapEntry::Watcher(watcher) => watcher.value.to_string(),
match &entry.entry {
DapEntry::Variable(dap) => {
let fallback_value = dap.value.clone();
let expression = dap
.evaluate_name
.clone()
.unwrap_or_else(|| dap.name.clone());
let frame_id = self.selected_stack_frame_id;
let task = self.session.update(cx, |session, cx| {
session.evaluate_variable_value(expression, frame_id, cx)
});
cx.spawn(async move |_, cx| {
let value = task.await.unwrap_or(fallback_value);
cx.update(|cx| {
cx.write_to_clipboard(ClipboardItem::new_string(value));
});
})
.detach();
}
DapEntry::Watcher(watcher) => {
cx.write_to_clipboard(ClipboardItem::new_string(watcher.value.to_string()));
}
DapEntry::Scope(_) => return,
};
cx.write_to_clipboard(ClipboardItem::new_string(variable_value));
}
}
fn edit_variable(&mut self, _: &EditVariable, window: &mut Window, cx: &mut Context<Self>) {
@@ -2592,3 +2592,58 @@ async fn test_refresh_watchers(executor: BackgroundExecutor, cx: &mut TestAppCon
assert_eq!(3, watcher.variables_reference);
});
}
#[gpui::test]
async fn test_evaluate_variable_value_uses_clipboard_context(
executor: BackgroundExecutor,
cx: &mut TestAppContext,
) {
init_test(cx);
let fs = FakeFs::new(executor.clone());
fs.insert_tree(
path!("/project"),
json!({
"index.js": "const debug = { foo: 1 };",
}),
)
.await;
let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = start_debug_session(&workspace, cx, |client| {
client.on_request::<Initialize, _>(move |_, _| {
Ok(dap::Capabilities {
supports_clipboard_context: Some(true),
..Default::default()
})
});
})
.unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<Evaluate, _>(move |_, args| {
assert_eq!("debug", args.expression);
assert_eq!(Some(dap::EvaluateArgumentsContext::Clipboard), args.context);
Ok(dap::EvaluateResponse {
result: "full value".to_string(),
type_: None,
presentation_hint: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
value_location_reference: None,
})
});
let value = session
.update(cx, |session, cx| {
session.evaluate_variable_value("debug".to_string(), Some(1), cx)
})
.await;
assert_eq!(Some("full value".to_string()), value);
}
+32
View File
@@ -2863,6 +2863,38 @@ impl Session {
})
}
/// Evaluates an expression to obtain its full value for copying.
pub fn evaluate_variable_value(
&mut self,
expression: String,
frame_id: Option<u64>,
cx: &mut Context<Self>,
) -> Task<Option<String>> {
let context = if self
.capabilities
.supports_clipboard_context
.unwrap_or_default()
{
EvaluateArgumentsContext::Clipboard
} else {
EvaluateArgumentsContext::Variables
};
let request = self.request(
EvaluateCommand {
expression,
frame_id,
context: Some(context),
source: None,
},
|_, response, _| response.ok(),
cx,
);
cx.background_spawn(async move {
let response = request.await?;
Some(response.result)
})
}
pub fn location(
&mut self,
reference: u64,