mirror of
https://github.com/openai/codex.git
synced 2026-11-03 19:53:25 +00:00
feat(tui): add transcript search
This commit is contained in:
@@ -315,6 +315,8 @@ pub struct TuiVimTextObjectKeymap {
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct TuiPagerKeymap {
|
||||
/// Start transcript search.
|
||||
pub start_search: Option<KeybindingsSpec>,
|
||||
/// Scroll up by one row.
|
||||
pub scroll_up: Option<KeybindingsSpec>,
|
||||
/// Scroll down by one row.
|
||||
@@ -331,14 +333,18 @@ pub struct TuiPagerKeymap {
|
||||
pub jump_top: Option<KeybindingsSpec>,
|
||||
/// Jump to the end.
|
||||
pub jump_bottom: Option<KeybindingsSpec>,
|
||||
/// Move to the previous user prompt in transcript search mode.
|
||||
pub previous_user_prompt: Option<KeybindingsSpec>,
|
||||
/// Move to the next user prompt in transcript search mode.
|
||||
pub next_user_prompt: Option<KeybindingsSpec>,
|
||||
/// Move to the next transcript search match.
|
||||
pub next_search_match: Option<KeybindingsSpec>,
|
||||
/// Move to the previous transcript search match.
|
||||
pub previous_search_match: Option<KeybindingsSpec>,
|
||||
/// Close the pager overlay.
|
||||
pub close: Option<KeybindingsSpec>,
|
||||
/// Close the transcript overlay via its dedicated toggle key.
|
||||
pub close_transcript: Option<KeybindingsSpec>,
|
||||
/// Jump to the previous user prompt in the transcript overlay.
|
||||
pub previous_user_prompt: Option<KeybindingsSpec>,
|
||||
/// Jump to the next user prompt in the transcript overlay.
|
||||
pub next_user_prompt: Option<KeybindingsSpec>,
|
||||
}
|
||||
|
||||
/// List selection context keybindings for popup-style selectable lists.
|
||||
|
||||
@@ -2842,12 +2842,15 @@
|
||||
"half_page_up": null,
|
||||
"jump_bottom": null,
|
||||
"jump_top": null,
|
||||
"next_search_match": null,
|
||||
"next_user_prompt": null,
|
||||
"page_down": null,
|
||||
"page_up": null,
|
||||
"previous_search_match": null,
|
||||
"previous_user_prompt": null,
|
||||
"scroll_down": null,
|
||||
"scroll_up": null
|
||||
"scroll_up": null,
|
||||
"start_search": null
|
||||
},
|
||||
"vim_normal": {
|
||||
"append_after_cursor": null,
|
||||
@@ -3528,12 +3531,15 @@
|
||||
"half_page_up": null,
|
||||
"jump_bottom": null,
|
||||
"jump_top": null,
|
||||
"next_search_match": null,
|
||||
"next_user_prompt": null,
|
||||
"page_down": null,
|
||||
"page_up": null,
|
||||
"previous_search_match": null,
|
||||
"previous_user_prompt": null,
|
||||
"scroll_down": null,
|
||||
"scroll_up": null
|
||||
"scroll_up": null,
|
||||
"start_search": null
|
||||
}
|
||||
},
|
||||
"vim_normal": {
|
||||
@@ -3753,13 +3759,21 @@
|
||||
],
|
||||
"description": "Jump to the beginning."
|
||||
},
|
||||
"next_search_match": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/KeybindingsSpec"
|
||||
}
|
||||
],
|
||||
"description": "Move to the next transcript search match."
|
||||
},
|
||||
"next_user_prompt": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/KeybindingsSpec"
|
||||
}
|
||||
],
|
||||
"description": "Jump to the next user prompt in the transcript overlay."
|
||||
"description": "Move to the next user prompt in transcript search mode."
|
||||
},
|
||||
"page_down": {
|
||||
"allOf": [
|
||||
@@ -3777,13 +3791,21 @@
|
||||
],
|
||||
"description": "Scroll up by one page."
|
||||
},
|
||||
"previous_search_match": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/KeybindingsSpec"
|
||||
}
|
||||
],
|
||||
"description": "Move to the previous transcript search match."
|
||||
},
|
||||
"previous_user_prompt": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/KeybindingsSpec"
|
||||
}
|
||||
],
|
||||
"description": "Jump to the previous user prompt in the transcript overlay."
|
||||
"description": "Move to the previous user prompt in transcript search mode."
|
||||
},
|
||||
"scroll_down": {
|
||||
"allOf": [
|
||||
@@ -3800,6 +3822,14 @@
|
||||
}
|
||||
],
|
||||
"description": "Scroll up by one row."
|
||||
},
|
||||
"start_search": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/KeybindingsSpec"
|
||||
}
|
||||
],
|
||||
"description": "Start transcript search."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -114,6 +114,24 @@ impl App {
|
||||
tui: &mut tui::Tui,
|
||||
event: TuiEvent,
|
||||
) -> Result<bool> {
|
||||
if let Some(Overlay::Transcript(transcript)) = &mut self.overlay {
|
||||
let key_event = match event {
|
||||
TuiEvent::Key(key_event) => Some(key_event),
|
||||
_ => None,
|
||||
};
|
||||
if transcript.search_is_active() {
|
||||
self.overlay_forward_event(tui, event)?;
|
||||
return Ok(true);
|
||||
}
|
||||
if let Some(key_event) = key_event
|
||||
&& transcript.is_start_search_key(key_event)
|
||||
{
|
||||
self.cancel_overlay_backtrack_preview();
|
||||
self.overlay_forward_event(tui, TuiEvent::Key(key_event))?;
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
if self.backtrack.overlay_preview_active {
|
||||
match event {
|
||||
TuiEvent::Key(KeyEvent {
|
||||
@@ -591,6 +609,17 @@ impl App {
|
||||
self.chat_widget.clear_esc_backtrack_hint();
|
||||
}
|
||||
|
||||
fn cancel_overlay_backtrack_preview(&mut self) {
|
||||
self.backtrack.overlay_preview_active = false;
|
||||
self.backtrack.primed = false;
|
||||
self.backtrack.base_id = None;
|
||||
self.backtrack.nth_user_message = usize::MAX;
|
||||
if let Some(Overlay::Transcript(transcript)) = &mut self.overlay {
|
||||
transcript.set_highlight_cell(/*cell*/ None);
|
||||
}
|
||||
self.chat_widget.clear_esc_backtrack_hint();
|
||||
}
|
||||
|
||||
pub(crate) fn apply_backtrack_selection(
|
||||
&mut self,
|
||||
tui: &mut tui::Tui,
|
||||
|
||||
+59
-17
@@ -211,6 +211,7 @@ pub(crate) struct VimTextObjectKeymap {
|
||||
/// Pager/overlay keybindings for transcript and static help views.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct PagerKeymap {
|
||||
pub(crate) start_search: Vec<KeyBinding>,
|
||||
pub(crate) scroll_up: Vec<KeyBinding>,
|
||||
pub(crate) scroll_down: Vec<KeyBinding>,
|
||||
pub(crate) page_up: Vec<KeyBinding>,
|
||||
@@ -219,10 +220,12 @@ pub(crate) struct PagerKeymap {
|
||||
pub(crate) half_page_down: Vec<KeyBinding>,
|
||||
pub(crate) jump_top: Vec<KeyBinding>,
|
||||
pub(crate) jump_bottom: Vec<KeyBinding>,
|
||||
pub(crate) close: Vec<KeyBinding>,
|
||||
pub(crate) close_transcript: Vec<KeyBinding>,
|
||||
pub(crate) previous_user_prompt: Vec<KeyBinding>,
|
||||
pub(crate) next_user_prompt: Vec<KeyBinding>,
|
||||
pub(crate) next_search_match: Vec<KeyBinding>,
|
||||
pub(crate) previous_search_match: Vec<KeyBinding>,
|
||||
pub(crate) close: Vec<KeyBinding>,
|
||||
pub(crate) close_transcript: Vec<KeyBinding>,
|
||||
}
|
||||
|
||||
/// Generic list picker keybindings shared across popup list views.
|
||||
@@ -741,6 +744,7 @@ impl RuntimeKeymap {
|
||||
};
|
||||
|
||||
let pager = PagerKeymap {
|
||||
start_search: resolve_local!(keymap, defaults, pager, start_search),
|
||||
scroll_up: resolve_local!(keymap, defaults, pager, scroll_up),
|
||||
scroll_down: resolve_local!(keymap, defaults, pager, scroll_down),
|
||||
page_up: resolve_local!(keymap, defaults, pager, page_up),
|
||||
@@ -749,10 +753,12 @@ impl RuntimeKeymap {
|
||||
half_page_down: resolve_local!(keymap, defaults, pager, half_page_down),
|
||||
jump_top: resolve_local!(keymap, defaults, pager, jump_top),
|
||||
jump_bottom: resolve_local!(keymap, defaults, pager, jump_bottom),
|
||||
close: resolve_local!(keymap, defaults, pager, close),
|
||||
close_transcript: resolve_local!(keymap, defaults, pager, close_transcript),
|
||||
previous_user_prompt: resolve_local!(keymap, defaults, pager, previous_user_prompt),
|
||||
next_user_prompt: resolve_local!(keymap, defaults, pager, next_user_prompt),
|
||||
next_search_match: resolve_local!(keymap, defaults, pager, next_search_match),
|
||||
previous_search_match: resolve_local!(keymap, defaults, pager, previous_search_match),
|
||||
close: resolve_local!(keymap, defaults, pager, close),
|
||||
close_transcript: resolve_local!(keymap, defaults, pager, close_transcript),
|
||||
};
|
||||
|
||||
let approval = ApprovalKeymap {
|
||||
@@ -1073,6 +1079,7 @@ impl RuntimeKeymap {
|
||||
cancel: default_bindings![plain(KeyCode::Esc)],
|
||||
},
|
||||
pager: PagerKeymap {
|
||||
start_search: default_bindings![plain(KeyCode::Char('/'))],
|
||||
scroll_up: default_bindings![plain(KeyCode::Up), plain(KeyCode::Char('k'))],
|
||||
scroll_down: default_bindings![plain(KeyCode::Down), plain(KeyCode::Char('j'))],
|
||||
page_up: default_bindings![
|
||||
@@ -1089,10 +1096,18 @@ impl RuntimeKeymap {
|
||||
half_page_down: default_bindings![ctrl(KeyCode::Char('d'))],
|
||||
jump_top: default_bindings![plain(KeyCode::Home)],
|
||||
jump_bottom: default_bindings![plain(KeyCode::End)],
|
||||
close: default_bindings![plain(KeyCode::Char('q')), ctrl(KeyCode::Char('c'))],
|
||||
close_transcript: default_bindings![ctrl(KeyCode::Char('t'))],
|
||||
previous_user_prompt: default_bindings![plain(KeyCode::Left), alt(KeyCode::Up)],
|
||||
next_user_prompt: default_bindings![plain(KeyCode::Right), alt(KeyCode::Down)],
|
||||
next_search_match: default_bindings![
|
||||
plain(KeyCode::Enter),
|
||||
plain(KeyCode::Char('n'))
|
||||
],
|
||||
previous_search_match: default_bindings![
|
||||
shift(KeyCode::Char('n')),
|
||||
plain(KeyCode::Char('N'))
|
||||
],
|
||||
close: default_bindings![plain(KeyCode::Char('q')), ctrl(KeyCode::Char('c'))],
|
||||
close_transcript: default_bindings![ctrl(KeyCode::Char('t'))],
|
||||
},
|
||||
list: ListKeymap {
|
||||
move_up: default_bindings![
|
||||
@@ -1567,9 +1582,10 @@ impl RuntimeKeymap {
|
||||
],
|
||||
)?;
|
||||
|
||||
validate_no_reserved(
|
||||
validate_unique(
|
||||
"pager",
|
||||
[
|
||||
("start_search", self.pager.start_search.as_slice()),
|
||||
("scroll_up", self.pager.scroll_up.as_slice()),
|
||||
("scroll_down", self.pager.scroll_down.as_slice()),
|
||||
("page_up", self.pager.page_up.as_slice()),
|
||||
@@ -1585,6 +1601,38 @@ impl RuntimeKeymap {
|
||||
self.pager.previous_user_prompt.as_slice(),
|
||||
),
|
||||
("next_user_prompt", self.pager.next_user_prompt.as_slice()),
|
||||
("next_search_match", self.pager.next_search_match.as_slice()),
|
||||
(
|
||||
"previous_search_match",
|
||||
self.pager.previous_search_match.as_slice(),
|
||||
),
|
||||
],
|
||||
)?;
|
||||
|
||||
validate_no_reserved(
|
||||
"pager",
|
||||
[
|
||||
("start_search", self.pager.start_search.as_slice()),
|
||||
("scroll_up", self.pager.scroll_up.as_slice()),
|
||||
("scroll_down", self.pager.scroll_down.as_slice()),
|
||||
("page_up", self.pager.page_up.as_slice()),
|
||||
("page_down", self.pager.page_down.as_slice()),
|
||||
("half_page_up", self.pager.half_page_up.as_slice()),
|
||||
("half_page_down", self.pager.half_page_down.as_slice()),
|
||||
("jump_top", self.pager.jump_top.as_slice()),
|
||||
("jump_bottom", self.pager.jump_bottom.as_slice()),
|
||||
("close", self.pager.close.as_slice()),
|
||||
("close_transcript", self.pager.close_transcript.as_slice()),
|
||||
(
|
||||
"previous_user_prompt",
|
||||
self.pager.previous_user_prompt.as_slice(),
|
||||
),
|
||||
("next_user_prompt", self.pager.next_user_prompt.as_slice()),
|
||||
("next_search_match", self.pager.next_search_match.as_slice()),
|
||||
(
|
||||
"previous_search_match",
|
||||
self.pager.previous_search_match.as_slice(),
|
||||
),
|
||||
],
|
||||
TRANSCRIPT_BACKTRACK_RESERVED_BINDINGS,
|
||||
[],
|
||||
@@ -1798,16 +1846,10 @@ const MAIN_RESERVED_BINDINGS: &[(&str, KeyBinding)] = &[
|
||||
),
|
||||
];
|
||||
|
||||
const TRANSCRIPT_BACKTRACK_RESERVED_BINDINGS: &[(&str, KeyBinding)] = &[
|
||||
(
|
||||
"fixed.transcript_edit_previous",
|
||||
key_hint::plain(KeyCode::Esc),
|
||||
),
|
||||
(
|
||||
"fixed.transcript_confirm_edit",
|
||||
key_hint::plain(KeyCode::Enter),
|
||||
),
|
||||
];
|
||||
const TRANSCRIPT_BACKTRACK_RESERVED_BINDINGS: &[(&str, KeyBinding)] = &[(
|
||||
"fixed.transcript_edit_previous",
|
||||
key_hint::plain(KeyCode::Esc),
|
||||
)];
|
||||
|
||||
/// Resolve one action with context -> global -> default precedence.
|
||||
///
|
||||
|
||||
@@ -167,6 +167,7 @@ pub(super) const KEYMAP_ACTIONS: &[KeymapActionDescriptor] = &[
|
||||
action("vim_text_object", "Vim text object", "single_quote", "Target enclosing single quotes."),
|
||||
action("vim_text_object", "Vim text object", "backtick", "Target enclosing backticks."),
|
||||
action("vim_text_object", "Vim text object", "cancel", "Cancel the pending text object."),
|
||||
action("pager", "Pager", "start_search", "Start transcript search."),
|
||||
action("pager", "Pager", "scroll_up", "Scroll up by one row."),
|
||||
action("pager", "Pager", "scroll_down", "Scroll down by one row."),
|
||||
action("pager", "Pager", "page_up", "Scroll up by one page."),
|
||||
@@ -175,6 +176,10 @@ pub(super) const KEYMAP_ACTIONS: &[KeymapActionDescriptor] = &[
|
||||
action("pager", "Pager", "half_page_down", "Scroll down by half a page."),
|
||||
action("pager", "Pager", "jump_top", "Jump to the beginning."),
|
||||
action("pager", "Pager", "jump_bottom", "Jump to the end."),
|
||||
action("pager", "Pager", "previous_user_prompt", "Move to the previous user prompt in transcript search mode."),
|
||||
action("pager", "Pager", "next_user_prompt", "Move to the next user prompt in transcript search mode."),
|
||||
action("pager", "Pager", "next_search_match", "Move to the next transcript search match."),
|
||||
action("pager", "Pager", "previous_search_match", "Move to the previous transcript search match."),
|
||||
action("pager", "Pager", "close", "Close the pager overlay."),
|
||||
action("pager", "Pager", "close_transcript", "Close the transcript overlay."),
|
||||
action("list", "List", "move_up", "Move list selection up."),
|
||||
@@ -310,6 +315,7 @@ pub(super) fn binding_slot<'a>(
|
||||
("vim_text_object", "single_quote") => Some(&mut keymap.vim_text_object.single_quote),
|
||||
("vim_text_object", "backtick") => Some(&mut keymap.vim_text_object.backtick),
|
||||
("vim_text_object", "cancel") => Some(&mut keymap.vim_text_object.cancel),
|
||||
("pager", "start_search") => Some(&mut keymap.pager.start_search),
|
||||
("pager", "scroll_up") => Some(&mut keymap.pager.scroll_up),
|
||||
("pager", "scroll_down") => Some(&mut keymap.pager.scroll_down),
|
||||
("pager", "page_up") => Some(&mut keymap.pager.page_up),
|
||||
@@ -318,6 +324,10 @@ pub(super) fn binding_slot<'a>(
|
||||
("pager", "half_page_down") => Some(&mut keymap.pager.half_page_down),
|
||||
("pager", "jump_top") => Some(&mut keymap.pager.jump_top),
|
||||
("pager", "jump_bottom") => Some(&mut keymap.pager.jump_bottom),
|
||||
("pager", "previous_user_prompt") => Some(&mut keymap.pager.previous_user_prompt),
|
||||
("pager", "next_user_prompt") => Some(&mut keymap.pager.next_user_prompt),
|
||||
("pager", "next_search_match") => Some(&mut keymap.pager.next_search_match),
|
||||
("pager", "previous_search_match") => Some(&mut keymap.pager.previous_search_match),
|
||||
("pager", "close") => Some(&mut keymap.pager.close),
|
||||
("pager", "close_transcript") => Some(&mut keymap.pager.close_transcript),
|
||||
("list", "move_up") => Some(&mut keymap.list.move_up),
|
||||
@@ -435,6 +445,7 @@ pub(super) fn bindings_for_action<'a>(
|
||||
("vim_text_object", "single_quote") => Some(runtime_keymap.vim_text_object.single_quote.as_slice()),
|
||||
("vim_text_object", "backtick") => Some(runtime_keymap.vim_text_object.backtick.as_slice()),
|
||||
("vim_text_object", "cancel") => Some(runtime_keymap.vim_text_object.cancel.as_slice()),
|
||||
("pager", "start_search") => Some(runtime_keymap.pager.start_search.as_slice()),
|
||||
("pager", "scroll_up") => Some(runtime_keymap.pager.scroll_up.as_slice()),
|
||||
("pager", "scroll_down") => Some(runtime_keymap.pager.scroll_down.as_slice()),
|
||||
("pager", "page_up") => Some(runtime_keymap.pager.page_up.as_slice()),
|
||||
@@ -443,6 +454,10 @@ pub(super) fn bindings_for_action<'a>(
|
||||
("pager", "half_page_down") => Some(runtime_keymap.pager.half_page_down.as_slice()),
|
||||
("pager", "jump_top") => Some(runtime_keymap.pager.jump_top.as_slice()),
|
||||
("pager", "jump_bottom") => Some(runtime_keymap.pager.jump_bottom.as_slice()),
|
||||
("pager", "previous_user_prompt") => Some(runtime_keymap.pager.previous_user_prompt.as_slice()),
|
||||
("pager", "next_user_prompt") => Some(runtime_keymap.pager.next_user_prompt.as_slice()),
|
||||
("pager", "next_search_match") => Some(runtime_keymap.pager.next_search_match.as_slice()),
|
||||
("pager", "previous_search_match") => Some(runtime_keymap.pager.previous_search_match.as_slice()),
|
||||
("pager", "close") => Some(runtime_keymap.pager.close.as_slice()),
|
||||
("pager", "close_transcript") => Some(runtime_keymap.pager.close_transcript.as_slice()),
|
||||
("list", "move_up") => Some(runtime_keymap.list.move_up.as_slice()),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -12,4 +12,4 @@ expression: snapshot
|
||||
2 +world
|
||||
─────────────────────────────────────────────────────────────────── 0 / 0 · 0% ─
|
||||
↑/↓ scroll ←/→ prompts pgup/pgdn page home/end jump
|
||||
q quit ctrl + o copy ⌥ + r raw esc/← prev
|
||||
q quit ctrl + o copy ⌥ + r raw esc/← prev / search
|
||||
|
||||
+1
-1
@@ -8,4 +8,4 @@ expression: "render_snapshot(&mut overlay, Rect::new(0, 0, 80, 8),)"
|
||||
|
||||
───────────────────────────────────────────────────────────────── 1 / 1 · 100% ─
|
||||
↑/↓ scroll ←/→ prompts pgup/pgdn page … Copied selected turn to clipboard
|
||||
q quit ctrl + o copy ⌥ + r raw esc/← prev
|
||||
q quit ctrl + o copy ⌥ + r raw esc/← prev / search
|
||||
|
||||
+1
-1
@@ -10,5 +10,5 @@ expression: term.backend()
|
||||
"~ "
|
||||
"───────────────────────── 0 / 0 · 100% ─"
|
||||
" ↑/↓ ←/→ pgup/pgdn home/end "
|
||||
" q ctrl + o ⌥ + r esc/← "
|
||||
" q ctrl + o ⌥ + r esc/← / "
|
||||
" "
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: tui/src/pager_overlay.rs
|
||||
expression: term.backend()
|
||||
---
|
||||
" Transcript · 1 prompt · 100% ────────────────────"
|
||||
"Search: needle 1/1"
|
||||
"› find this prompt "
|
||||
" "
|
||||
" "
|
||||
"• assistant needle result "
|
||||
"──────────────────────────────────────────────────"
|
||||
" ↑/↓ ←/→ pgup/pgdn home/end "
|
||||
" ctrl + t esc enter shift + n / "
|
||||
" "
|
||||
+1
-1
@@ -10,5 +10,5 @@ expression: term.backend()
|
||||
"gamma "
|
||||
"───────────────────────── 0 / 0 · 100% ─"
|
||||
" ↑/↓ ←/→ pgup/pgdn home/end "
|
||||
" q ctrl + o ⌥ + r esc/← "
|
||||
" q ctrl + o ⌥ + r esc/← / "
|
||||
" "
|
||||
|
||||
Reference in New Issue
Block a user