Skip to content

Commit 4778d61

Browse files
Fix copy button not working for REPL error output (#40669)
## Description Fixes the copy button functionality in REPL interactive mode error output sections. When executing Python code that produces errors in the REPL (e.g., `NameError`), the copy button in the error output section was unresponsive. The stdout/stderr copy button worked correctly, but the error traceback section copy button had no effect when clicked. Fixes #40207 ## Changes Modified the following: src/outputs.rs: Fixed context issues in render_output_controls by replacing cx.listener() with simple closures, and added custom button implementation for ErrorOutput that copies/opens the complete error (name + message + traceback) src/outputs/plain.rs: Made full_text() method public to allow access from button handlers src/outputs/user_error.rs: Added Clone derive to ErrorView struct and removed a couple pieces of commented code ## Why This Matters The copy button was clearly broken and it is useful to have for REPL workflows. Users could potentially need to copy error messages for a variety of reasons. ## Testing See attached demo for proof that the fix is working as intended. (this is my first ever commit, if there are additional test cases I need to write or run, please let me know!) https://github.com/user-attachments/assets/da158205-4119-47eb-a271-196ef8d196e4 Release Notes: - Fixed copy button not working for REPL error output
1 parent 46c5d51 commit 4778d61

File tree

3 files changed

+91
-15
lines changed

3 files changed

+91
-15
lines changed

crates/repl/src/outputs.rs

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ use gpui::{AnyElement, ClipboardItem, Entity, Render, WeakEntity};
3838
use language::Buffer;
3939
use runtimelib::{ExecutionState, JupyterMessageContent, MimeBundle, MimeType};
4040
use ui::{
41-
CommonAnimationExt, Context, IntoElement, Styled, Tooltip, Window, div, prelude::*, v_flex,
41+
ButtonStyle, CommonAnimationExt, Context, IconButton, IconName, IntoElement, Styled, Tooltip,
42+
Window, div, h_flex, prelude::*, v_flex,
4243
};
4344

4445
mod image;
@@ -146,13 +147,13 @@ impl Output {
146147
IconButton::new(ElementId::Name("copy-output".into()), IconName::Copy)
147148
.style(ButtonStyle::Transparent)
148149
.tooltip(Tooltip::text("Copy Output"))
149-
.on_click(cx.listener(move |_, _, window, cx| {
150+
.on_click(move |_, window, cx| {
150151
let clipboard_content = v.clipboard_content(window, cx);
151152

152153
if let Some(clipboard_content) = clipboard_content.as_ref() {
153154
cx.write_to_clipboard(clipboard_content.clone());
154155
}
155-
})),
156+
}),
156157
)
157158
})
158159
.when(v.has_buffer_content(window, cx), |el| {
@@ -164,10 +165,9 @@ impl Output {
164165
)
165166
.style(ButtonStyle::Transparent)
166167
.tooltip(Tooltip::text("Open in Buffer"))
167-
.on_click(cx.listener({
168+
.on_click({
168169
let workspace = workspace.clone();
169-
170-
move |_, _, window, cx| {
170+
move |_, window, cx| {
171171
let buffer_content =
172172
v.update(cx, |item, cx| item.buffer_content(window, cx));
173173

@@ -193,7 +193,7 @@ impl Output {
193193
.ok();
194194
}
195195
}
196-
})),
196+
}),
197197
)
198198
})
199199
.into_any_element(),
@@ -237,7 +237,87 @@ impl Output {
237237
Self::render_output_controls(content.clone(), workspace, window, cx)
238238
}
239239
Self::ErrorOutput(err) => {
240-
Self::render_output_controls(err.traceback.clone(), workspace, window, cx)
240+
// Add buttons for the traceback section
241+
Some(
242+
h_flex()
243+
.pl_1()
244+
.child(
245+
IconButton::new(
246+
ElementId::Name("copy-full-error-traceback".into()),
247+
IconName::Copy,
248+
)
249+
.style(ButtonStyle::Transparent)
250+
.tooltip(Tooltip::text("Copy Full Error"))
251+
.on_click({
252+
let ename = err.ename.clone();
253+
let evalue = err.evalue.clone();
254+
let traceback = err.traceback.clone();
255+
move |_, _window, cx| {
256+
let traceback_text = traceback.read(cx).full_text();
257+
let full_error =
258+
format!("{}: {}\n{}", ename, evalue, traceback_text);
259+
let clipboard_content =
260+
ClipboardItem::new_string(full_error);
261+
cx.write_to_clipboard(clipboard_content);
262+
}
263+
}),
264+
)
265+
.child(
266+
IconButton::new(
267+
ElementId::Name("open-full-error-in-buffer-traceback".into()),
268+
IconName::FileTextOutlined,
269+
)
270+
.style(ButtonStyle::Transparent)
271+
.tooltip(Tooltip::text("Open Full Error in Buffer"))
272+
.on_click({
273+
let ename = err.ename.clone();
274+
let evalue = err.evalue.clone();
275+
let traceback = err.traceback.clone();
276+
move |_, window, cx| {
277+
if let Some(workspace) = workspace.upgrade() {
278+
let traceback_text = traceback.read(cx).full_text();
279+
let full_error = format!(
280+
"{}: {}\n{}",
281+
ename, evalue, traceback_text
282+
);
283+
let buffer = cx.new(|cx| {
284+
let mut buffer = Buffer::local(full_error, cx)
285+
.with_language(
286+
language::PLAIN_TEXT.clone(),
287+
cx,
288+
);
289+
buffer.set_capability(
290+
language::Capability::ReadOnly,
291+
cx,
292+
);
293+
buffer
294+
});
295+
let editor = Box::new(cx.new(|cx| {
296+
let multibuffer = cx.new(|cx| {
297+
let mut multi_buffer =
298+
MultiBuffer::singleton(buffer.clone(), cx);
299+
multi_buffer
300+
.set_title("Full Error".to_string(), cx);
301+
multi_buffer
302+
});
303+
Editor::for_multibuffer(
304+
multibuffer,
305+
None,
306+
window,
307+
cx,
308+
)
309+
}));
310+
workspace.update(cx, |workspace, cx| {
311+
workspace.add_item_to_active_pane(
312+
editor, None, true, window, cx,
313+
);
314+
});
315+
}
316+
}
317+
}),
318+
)
319+
.into_any_element(),
320+
)
241321
}
242322
Self::Message(_) => None,
243323
Self::Table { content, .. } => {

crates/repl/src/outputs/plain.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ impl TerminalOutput {
197197
}
198198
}
199199

200-
fn full_text(&self) -> String {
200+
pub fn full_text(&self) -> String {
201201
fn sanitize(mut line: String) -> Option<String> {
202202
line.retain(|ch| ch != '\u{0}' && ch != '\r');
203203
if line.trim().is_empty() {

crates/repl/src/outputs/user_error.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use ui::{Label, h_flex, prelude::*, v_flex};
44
use crate::outputs::plain::TerminalOutput;
55

66
/// Userspace error from the kernel
7+
#[derive(Clone)]
78
pub struct ErrorView {
89
pub ename: String,
910
pub evalue: String,
@@ -24,15 +25,10 @@ impl ErrorView {
2425
.font_buffer(cx)
2526
.child(
2627
Label::new(format!("{}: ", self.ename.clone()))
27-
// .size(LabelSize::Large)
2828
.color(Color::Error)
2929
.weight(FontWeight::BOLD),
3030
)
31-
.child(
32-
Label::new(self.evalue.clone())
33-
// .size(LabelSize::Large)
34-
.weight(FontWeight::BOLD),
35-
),
31+
.child(Label::new(self.evalue.clone()).weight(FontWeight::BOLD)),
3632
)
3733
.child(
3834
div()

0 commit comments

Comments
 (0)