mirror of
https://github.com/openai/codex.git
synced 2026-11-10 21:00:47 +00:00
Add keyboard copy selection to the owned transcript (#50434)
## What changed - Make `/copy` select the latest completed response directly in the owned transcript. Navigate user messages, code blocks, and blockquotes with arrow keys or `j`/`k`, use `g`/`G` to jump to the ends, and press Enter to copy or Escape to close. - Highlight selected blocks and keep surrounding context visible during navigation and resizing. Preserve source payloads for code and quotes, including rendered Mermaid diagrams and fenced Markdown tables. - Pause queued submissions until copy mode closes, and report clipboard results in the transcript footer. ## Testing Add tests for copy routing, keyboard navigation, highlighting and resize behavior, source whitespace and line endings, clipboard completion, and queued-input resumption. GitOrigin-RevId: bd4f58ec17568f95d0e91014e5ddba33ca8bd57c
This commit is contained in:
committed by
copyberry
parent
66561d0301
commit
c536ffcb18
@@ -8,6 +8,7 @@
|
||||
mod activity;
|
||||
mod bookmark;
|
||||
mod composer_gap;
|
||||
mod copy_mode;
|
||||
mod disclosure;
|
||||
mod follow_control;
|
||||
mod footer;
|
||||
@@ -103,6 +104,7 @@ pub(crate) struct TranscriptView {
|
||||
suppressed_prompt_header: Option<prompt_header::SuppressedHeader>,
|
||||
visible: Vec<VisibleRow>,
|
||||
selection: Option<Selection>,
|
||||
copy_mode: Option<copy_mode::CopyMode>,
|
||||
held_reading: Option<ViewSnapshot>,
|
||||
search: Search,
|
||||
detailed: bool,
|
||||
@@ -138,6 +140,7 @@ impl Default for TranscriptView {
|
||||
suppressed_prompt_header: None,
|
||||
visible: Vec::new(),
|
||||
selection: None,
|
||||
copy_mode: None,
|
||||
held_reading: None,
|
||||
search: Search::default(),
|
||||
detailed: false,
|
||||
@@ -177,15 +180,21 @@ impl TranscriptView {
|
||||
let cells = snapshot.as_deref().unwrap_or(cells);
|
||||
let suppressed_prompt_header = self.suppressed_prompt_header.take();
|
||||
Clear.render(area, buf);
|
||||
let previous_height = self.area.height;
|
||||
let resized = self.area.width != area.width || self.area.bottom() != area.bottom();
|
||||
self.prepare_width(area.width);
|
||||
self.area = area;
|
||||
self.normalize_selection(cells);
|
||||
if resized {
|
||||
self.reveal_copy_context(cells);
|
||||
}
|
||||
self.visible.clear();
|
||||
if area.is_empty() {
|
||||
self.tail_visible = false;
|
||||
return;
|
||||
}
|
||||
let initial_start = self.start(cells);
|
||||
let initial_position = self.position;
|
||||
let mut start = initial_start;
|
||||
let mut body = area;
|
||||
let header_position = prompt_header::SuppressedHeader {
|
||||
@@ -200,7 +209,12 @@ impl TranscriptView {
|
||||
self.suppressed_prompt_header = Some(header_position);
|
||||
} else if area.height >= 4 && prompt_header::line(cells, start.0, area.width).is_some() {
|
||||
body = Rect::new(area.x, area.y + 1, area.width, area.height - 1);
|
||||
let resized = resized || previous_height != body.height;
|
||||
self.area = body;
|
||||
// Copy targets must also leave context after the prompt reserves its row.
|
||||
if resized {
|
||||
self.reveal_copy_context(cells);
|
||||
}
|
||||
// Following may advance into the next turn after reserving the header row.
|
||||
start = self.start(cells);
|
||||
if let Some(header) = prompt_header::line(cells, start.0, area.width) {
|
||||
@@ -209,6 +223,9 @@ impl TranscriptView {
|
||||
body = area;
|
||||
self.area = area;
|
||||
start = initial_start;
|
||||
if resized && self.copy_mode.is_some() {
|
||||
self.position = initial_position;
|
||||
}
|
||||
self.suppressed_prompt_header = Some(header_position);
|
||||
}
|
||||
}
|
||||
@@ -336,6 +353,7 @@ impl TranscriptView {
|
||||
return;
|
||||
}
|
||||
self.selection = None;
|
||||
self.copy_mode = None;
|
||||
self.release_live_reading();
|
||||
self.cache.clear();
|
||||
self.suppressed_prompt_header = None;
|
||||
@@ -381,6 +399,7 @@ impl TranscriptView {
|
||||
self.cancel_beginning();
|
||||
self.position = Position::Latest;
|
||||
self.selection = None;
|
||||
self.copy_mode = None;
|
||||
self.release_live_reading();
|
||||
self.unseen_activity = false;
|
||||
self.disclosure.focused = None;
|
||||
|
||||
Reference in New Issue
Block a user