From d57014ec50a10dbbb283d17fe1c1bc39c4a781ba Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 22 Aug 2025 10:44:07 -0700 Subject: [PATCH] go back in history --- codex-rs/tui/src/backtrack_helpers.rs | 97 +++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 codex-rs/tui/src/backtrack_helpers.rs diff --git a/codex-rs/tui/src/backtrack_helpers.rs b/codex-rs/tui/src/backtrack_helpers.rs new file mode 100644 index 0000000000..64e42d435c --- /dev/null +++ b/codex-rs/tui/src/backtrack_helpers.rs @@ -0,0 +1,97 @@ +use ratatui::text::Line; + +/// Find the header index for the Nth last user message in the transcript. +/// Returns `None` if `n == 0` or there are fewer than `n` user messages. +pub(crate) fn find_nth_last_user_header_index(lines: &[Line<'_>], n: usize) -> Option { + if n == 0 { + return None; + } + let mut found = 0usize; + for (idx, line) in lines.iter().enumerate().rev() { + let content: String = line + .spans + .iter() + .map(|s| s.content.as_ref()) + .collect::>() + .join(""); + if content.trim() == "user" { + found += 1; + if found == n { + return Some(idx); + } + } + } + None +} + +/// Extract the text content of the Nth last user message. +/// The message body is considered to be the lines following the "user" header +/// until the first blank line. +pub(crate) fn nth_last_user_text(lines: &[Line<'_>], n: usize) -> Option { + let header_idx = find_nth_last_user_header_index(lines, n)?; + extract_message_text_after_header(lines, header_idx) +} + +/// Extract message text starting after `header_idx` until the first blank line. +pub(crate) fn extract_message_text_after_header( + lines: &[Line<'_>], + header_idx: usize, +) -> Option { + let start = header_idx + 1; + let mut out: Vec = Vec::new(); + for line in lines.iter().skip(start) { + let is_blank = line + .spans + .iter() + .all(|s| s.content.as_ref().trim().is_empty()); + if is_blank { + break; + } + let text: String = line + .spans + .iter() + .map(|s| s.content.as_ref()) + .collect::>() + .join(""); + out.push(text); + } + if out.is_empty() { None } else { Some(out.join("\n")) } +} + +/// Compute the wrapped display-line offset before `header_idx`, for a given width. +pub(crate) fn wrapped_offset_before( + lines: &[Line<'_>], + header_idx: usize, + width: u16, +) -> usize { + let before = &lines[0..header_idx]; + crate::insert_history::word_wrap_lines(before, width).len() +} + +/// Given a header index, return the inclusive range for the message block +/// [header_idx, end) where end is the first blank line after the header or the +/// end of the transcript. +pub(crate) fn highlight_range_from_header(lines: &[Line<'_>], header_idx: usize) -> (usize, usize) { + let mut end = header_idx + 1; + while end < lines.len() { + let is_blank = lines[end] + .spans + .iter() + .all(|s| s.content.as_ref().trim().is_empty()); + if is_blank { + break; + } + end += 1; + } + (header_idx, end) +} + +/// Convenience: compute the highlight range for the Nth last user message. +pub(crate) fn highlight_range_for_nth_last_user( + lines: &[Line<'_>], + n: usize, +) -> Option<(usize, usize)> { + let header = find_nth_last_user_header_index(lines, n)?; + Some(highlight_range_from_header(lines, header)) +} +