mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
tui2: copy selection dismisses highlight (#8718)
Clicking the transcript copy pill or pressing the copy shortcut now
copies the selected transcript text and clears the highlight.
Show transient footer feedback ("Copied"/"Copy failed") after a copy
attempt, with logic in transcript_copy_action to keep app.rs smaller and
closer to tui for long-term diffs.
Update footer snapshots and add tiny unit tests for feedback expiry.
https://github.com/user-attachments/assets/c36c8163-11c5-476b-b388-e6fbe0ff6034
This commit is contained in:
1 parent
5678213058
commit
181ff89cbd
8 files changed
+319
-46
No files matched your search
@@ -1,6 +1,7 @@
|
||||
use crate::key_hint;
|
||||
use crate::key_hint::KeyBinding;
|
||||
use crate::key_hint::has_ctrl_or_alt;
|
||||
use crate::transcript_copy_action::TranscriptCopyFeedback;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyEventKind;
|
||||
@@ -124,6 +125,7 @@ pub(crate) struct ChatComposer {
|
||||
transcript_selection_active: bool,
|
||||
transcript_scroll_position: Option<(usize, usize)>,
|
||||
transcript_copy_selection_key: KeyBinding,
|
||||
transcript_copy_feedback: Option<TranscriptCopyFeedback>,
|
||||
skills: Option<Vec<SkillMetadata>>,
|
||||
dismissed_skill_popup_token: Option<String>,
|
||||
}
|
||||
@@ -176,6 +178,7 @@ impl ChatComposer {
|
||||
transcript_selection_active: false,
|
||||
transcript_scroll_position: None,
|
||||
transcript_copy_selection_key: key_hint::ctrl_shift(KeyCode::Char('c')),
|
||||
transcript_copy_feedback: None,
|
||||
skills: None,
|
||||
dismissed_skill_popup_token: None,
|
||||
};
|
||||
@@ -1545,6 +1548,7 @@ impl ChatComposer {
|
||||
transcript_selection_active: self.transcript_selection_active,
|
||||
transcript_scroll_position: self.transcript_scroll_position,
|
||||
transcript_copy_selection_key: self.transcript_copy_selection_key,
|
||||
transcript_copy_feedback: self.transcript_copy_feedback,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1577,11 +1581,13 @@ impl ChatComposer {
|
||||
selection_active: bool,
|
||||
scroll_position: Option<(usize, usize)>,
|
||||
copy_selection_key: KeyBinding,
|
||||
copy_feedback: Option<TranscriptCopyFeedback>,
|
||||
) -> bool {
|
||||
if self.transcript_scrolled == scrolled
|
||||
&& self.transcript_selection_active == selection_active
|
||||
&& self.transcript_scroll_position == scroll_position
|
||||
&& self.transcript_copy_selection_key == copy_selection_key
|
||||
&& self.transcript_copy_feedback == copy_feedback
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1590,6 +1596,7 @@ impl ChatComposer {
|
||||
self.transcript_selection_active = selection_active;
|
||||
self.transcript_scroll_position = scroll_position;
|
||||
self.transcript_copy_selection_key = copy_selection_key;
|
||||
self.transcript_copy_feedback = copy_feedback;
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::key_hint;
|
||||
use crate::key_hint::KeyBinding;
|
||||
use crate::render::line_utils::prefix_lines;
|
||||
use crate::status::format_tokens_compact;
|
||||
use crate::transcript_copy_action::TranscriptCopyFeedback;
|
||||
use crate::ui_consts::FOOTER_INDENT_COLS;
|
||||
use crossterm::event::KeyCode;
|
||||
use ratatui::buffer::Buffer;
|
||||
@@ -26,6 +27,7 @@ pub(crate) struct FooterProps {
|
||||
pub(crate) transcript_selection_active: bool,
|
||||
pub(crate) transcript_scroll_position: Option<(usize, usize)>,
|
||||
pub(crate) transcript_copy_selection_key: KeyBinding,
|
||||
pub(crate) transcript_copy_feedback: Option<TranscriptCopyFeedback>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
@@ -80,11 +82,26 @@ pub(crate) fn render_footer(area: Rect, buf: &mut Buffer, props: FooterProps) {
|
||||
}
|
||||
|
||||
fn footer_lines(props: FooterProps) -> Vec<Line<'static>> {
|
||||
fn apply_copy_feedback(lines: &mut [Line<'static>], feedback: Option<TranscriptCopyFeedback>) {
|
||||
let Some(line) = lines.first_mut() else {
|
||||
return;
|
||||
};
|
||||
let Some(feedback) = feedback else {
|
||||
return;
|
||||
};
|
||||
|
||||
line.push_span(" · ".dim());
|
||||
match feedback {
|
||||
TranscriptCopyFeedback::Copied => line.push_span("Copied".green().bold()),
|
||||
TranscriptCopyFeedback::Failed => line.push_span("Copy failed".red().bold()),
|
||||
}
|
||||
}
|
||||
|
||||
// Show the context indicator on the left, appended after the primary hint
|
||||
// (e.g., "? for shortcuts"). Keep it visible even when typing (i.e., when
|
||||
// the shortcut hint is hidden). Hide it only for the multi-line
|
||||
// ShortcutOverlay.
|
||||
match props.mode {
|
||||
let mut lines = match props.mode {
|
||||
FooterMode::CtrlCReminder => vec![ctrl_c_reminder_line(CtrlCReminderState {
|
||||
is_task_running: props.is_task_running,
|
||||
})],
|
||||
@@ -139,7 +156,9 @@ fn footer_lines(props: FooterProps) -> Vec<Line<'static>> {
|
||||
props.context_window_percent,
|
||||
props.context_window_used_tokens,
|
||||
)],
|
||||
}
|
||||
};
|
||||
apply_copy_feedback(&mut lines, props.transcript_copy_feedback);
|
||||
lines
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -469,6 +488,7 @@ mod tests {
|
||||
transcript_selection_active: false,
|
||||
transcript_scroll_position: None,
|
||||
transcript_copy_selection_key: key_hint::ctrl_shift(KeyCode::Char('c')),
|
||||
transcript_copy_feedback: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -485,6 +505,7 @@ mod tests {
|
||||
transcript_selection_active: true,
|
||||
transcript_scroll_position: Some((3, 42)),
|
||||
transcript_copy_selection_key: key_hint::ctrl_shift(KeyCode::Char('c')),
|
||||
transcript_copy_feedback: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -501,6 +522,7 @@ mod tests {
|
||||
transcript_selection_active: false,
|
||||
transcript_scroll_position: None,
|
||||
transcript_copy_selection_key: key_hint::ctrl_shift(KeyCode::Char('c')),
|
||||
transcript_copy_feedback: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -517,6 +539,7 @@ mod tests {
|
||||
transcript_selection_active: false,
|
||||
transcript_scroll_position: None,
|
||||
transcript_copy_selection_key: key_hint::ctrl_shift(KeyCode::Char('c')),
|
||||
transcript_copy_feedback: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -533,6 +556,7 @@ mod tests {
|
||||
transcript_selection_active: false,
|
||||
transcript_scroll_position: None,
|
||||
transcript_copy_selection_key: key_hint::ctrl_shift(KeyCode::Char('c')),
|
||||
transcript_copy_feedback: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -549,6 +573,7 @@ mod tests {
|
||||
transcript_selection_active: false,
|
||||
transcript_scroll_position: None,
|
||||
transcript_copy_selection_key: key_hint::ctrl_shift(KeyCode::Char('c')),
|
||||
transcript_copy_feedback: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -565,6 +590,7 @@ mod tests {
|
||||
transcript_selection_active: false,
|
||||
transcript_scroll_position: None,
|
||||
transcript_copy_selection_key: key_hint::ctrl_shift(KeyCode::Char('c')),
|
||||
transcript_copy_feedback: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -581,6 +607,7 @@ mod tests {
|
||||
transcript_selection_active: false,
|
||||
transcript_scroll_position: None,
|
||||
transcript_copy_selection_key: key_hint::ctrl_shift(KeyCode::Char('c')),
|
||||
transcript_copy_feedback: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -597,6 +624,24 @@ mod tests {
|
||||
transcript_selection_active: false,
|
||||
transcript_scroll_position: None,
|
||||
transcript_copy_selection_key: key_hint::ctrl_shift(KeyCode::Char('c')),
|
||||
transcript_copy_feedback: None,
|
||||
},
|
||||
);
|
||||
|
||||
snapshot_footer(
|
||||
"footer_copy_feedback_copied",
|
||||
FooterProps {
|
||||
mode: FooterMode::ShortcutSummary,
|
||||
esc_backtrack_hint: false,
|
||||
use_shift_enter_hint: false,
|
||||
is_task_running: false,
|
||||
context_window_percent: None,
|
||||
context_window_used_tokens: None,
|
||||
transcript_scrolled: false,
|
||||
transcript_selection_active: false,
|
||||
transcript_scroll_position: None,
|
||||
transcript_copy_selection_key: key_hint::ctrl_shift(KeyCode::Char('c')),
|
||||
transcript_copy_feedback: Some(TranscriptCopyFeedback::Copied),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -388,12 +388,14 @@ impl BottomPane {
|
||||
selection_active: bool,
|
||||
scroll_position: Option<(usize, usize)>,
|
||||
copy_selection_key: crate::key_hint::KeyBinding,
|
||||
copy_feedback: Option<crate::transcript_copy_action::TranscriptCopyFeedback>,
|
||||
) {
|
||||
let updated = self.composer.set_transcript_ui_state(
|
||||
scrolled,
|
||||
selection_active,
|
||||
scroll_position,
|
||||
copy_selection_key,
|
||||
copy_feedback,
|
||||
);
|
||||
if updated {
|
||||
self.request_redraw();
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: tui2/src/bottom_pane/footer.rs
|
||||
assertion_line: 473
|
||||
expression: terminal.backend()
|
||||
---
|
||||
" 100% context left · ? for shortcuts · Copied "
|
||||
Reference in new issue
Block a user