mirror of
https://github.com/openai/codex.git
synced 2026-05-01 03:42:05 +03:00
Persist text element ranges and attached images across history/resume (#9116)
**Summary** - Backtrack selection now rehydrates `text_elements` and `local_image_paths` from the chosen user history cell so Esc‑Esc history edits preserve image placeholders and attachments. - Composer prefill uses the preserved elements/attachments in both `tui` and `tui2`. - Extended backtrack selection tests to cover image placeholder elements and local image paths. **Changes** - `tui/src/app_backtrack.rs`: Backtrack selection now carries text elements + local image paths; composer prefill uses them (removes TODO). - `tui2/src/app_backtrack.rs`: Same as above. - `tui/src/app.rs`: Updated backtrack test to assert restored elements/paths. - `tui2/src/app.rs`: Same test updates. ### The original scope of this PR (threading text elements and image attachments through the codex harness thoroughly/persistently) was broken into the following PRs other than this one: The diff of this PR was reduced by changing types in a starter PR: https://github.com/openai/codex/pull/9235 Then text element metadata was added to protocol, app server, and core in this PR: https://github.com/openai/codex/pull/9331 Then the end-to-end flow was completed by wiring TUI/TUI2 input, history, and restore behavior in https://github.com/openai/codex/pull/9393 Prompt expansion was supported in this PR: https://github.com/openai/codex/pull/9518 TextElement optional placeholder field was protected in https://github.com/openai/codex/pull/9545
This commit is contained in:
@@ -212,6 +212,7 @@ pub(crate) struct ChatComposer {
|
||||
pending_pastes: Vec<(String, String)>,
|
||||
large_paste_counters: HashMap<usize, usize>,
|
||||
has_focus: bool,
|
||||
/// Invariant: attached images are labeled `[Image #1]..[Image #N]` in vec order.
|
||||
attached_images: Vec<AttachedImage>,
|
||||
placeholder_text: String,
|
||||
is_task_running: bool,
|
||||
@@ -475,8 +476,9 @@ impl ChatComposer {
|
||||
|
||||
/// Replace the composer content with text from an external editor.
|
||||
/// Clears pending paste placeholders and keeps only attachments whose
|
||||
/// placeholder labels still appear in the new text. Cursor is placed at
|
||||
/// the end after rebuilding elements.
|
||||
/// placeholder labels still appear in the new text. Image placeholders
|
||||
/// are renumbered to `[Image #1]..[Image #N]`. Cursor is placed at the end
|
||||
/// after rebuilding elements.
|
||||
pub(crate) fn apply_external_edit(&mut self, text: String) {
|
||||
self.pending_pastes.clear();
|
||||
|
||||
@@ -538,6 +540,8 @@ impl ChatComposer {
|
||||
self.textarea.insert_str(&text[idx..]);
|
||||
}
|
||||
|
||||
// Keep image placeholders normalized to [Image #1].. in attachment order.
|
||||
self.relabel_attached_images_and_update_placeholders();
|
||||
self.textarea.set_cursor(self.textarea.text().len());
|
||||
self.sync_popups();
|
||||
}
|
||||
@@ -6081,7 +6085,7 @@ mod tests {
|
||||
false,
|
||||
);
|
||||
|
||||
let placeholder = "[image 10x10]".to_string();
|
||||
let placeholder = local_image_label_text(1);
|
||||
composer.textarea.insert_element(&placeholder);
|
||||
composer.attached_images.push(AttachedImage {
|
||||
placeholder: placeholder.clone(),
|
||||
@@ -6115,7 +6119,7 @@ mod tests {
|
||||
false,
|
||||
);
|
||||
|
||||
let placeholder = "[image 10x10]".to_string();
|
||||
let placeholder = local_image_label_text(1);
|
||||
composer.textarea.insert_element(&placeholder);
|
||||
composer.attached_images.push(AttachedImage {
|
||||
placeholder: placeholder.clone(),
|
||||
@@ -6165,7 +6169,7 @@ mod tests {
|
||||
false,
|
||||
);
|
||||
|
||||
let placeholder = "[image 10x10]".to_string();
|
||||
let placeholder = local_image_label_text(1);
|
||||
composer.textarea.insert_element(&placeholder);
|
||||
composer.attached_images.push(AttachedImage {
|
||||
placeholder: placeholder.clone(),
|
||||
|
||||
Reference in New Issue
Block a user