Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 87 additions & 2 deletions codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions codex-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ pretty_assertions = "1.4.1"
pulldown-cmark = "0.10"
rand = "0.9"
ratatui = "0.29.0"
ratatui-core = "0.1.0"
ratatui-macros = "0.6.0"
regex = "1.12.2"
regex-lite = "0.1.8"
Expand Down Expand Up @@ -219,6 +220,7 @@ tree-sitter = "0.25.10"
tree-sitter-bash = "0.25"
tree-sitter-highlight = "0.25.10"
ts-rs = "11"
tui-scrollbar = "0.2.1"
uds_windows = "1.1.0"
unicode-segmentation = "1.12.0"
unicode-width = "0.2"
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/tui2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ ratatui = { workspace = true, features = [
"unstable-rendered-line-info",
"unstable-widget-ref",
] }
ratatui-core = { workspace = true }
ratatui-macros = { workspace = true }
regex-lite = { workspace = true }
reqwest = { version = "0.12", features = ["json"] }
Expand All @@ -73,6 +74,7 @@ strum_macros = { workspace = true }
supports-color = { workspace = true }
tempfile = { workspace = true }
textwrap = { workspace = true }
tui-scrollbar = { workspace = true }
tokio = { workspace = true, features = [
"io-std",
"macros",
Expand Down
63 changes: 57 additions & 6 deletions codex-rs/tui2/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ use crate::transcript_copy_action::TranscriptCopyAction;
use crate::transcript_copy_action::TranscriptCopyFeedback;
use crate::transcript_copy_ui::TranscriptCopyUi;
use crate::transcript_multi_click::TranscriptMultiClick;
use crate::transcript_scrollbar::render_transcript_scrollbar_if_active;
use crate::transcript_scrollbar::split_transcript_area;
use crate::transcript_scrollbar_ui::TranscriptScrollbarMouseEvent;
use crate::transcript_scrollbar_ui::TranscriptScrollbarMouseHandling;
use crate::transcript_scrollbar_ui::TranscriptScrollbarUi;
use crate::transcript_selection::TRANSCRIPT_GUTTER_COLS;
use crate::transcript_selection::TranscriptSelection;
use crate::transcript_selection::TranscriptSelectionPoint;
Expand Down Expand Up @@ -337,6 +342,7 @@ pub(crate) struct App {
transcript_total_lines: usize,
transcript_copy_ui: TranscriptCopyUi,
transcript_copy_action: TranscriptCopyAction,
transcript_scrollbar_ui: TranscriptScrollbarUi,

// Pager overlay state (Transcript or Static like Diff)
pub(crate) overlay: Option<Overlay>,
Expand Down Expand Up @@ -503,6 +509,7 @@ impl App {
transcript_total_lines: 0,
transcript_copy_ui: TranscriptCopyUi::new_with_shortcut(copy_selection_shortcut),
transcript_copy_action: TranscriptCopyAction::default(),
transcript_scrollbar_ui: TranscriptScrollbarUi::default(),
overlay: None,
deferred_history_lines: Vec::new(),
has_emitted_history_lines: false,
Expand Down Expand Up @@ -708,18 +715,19 @@ impl App {
return area.y;
}

let transcript_area = Rect {
let transcript_full_area = Rect {
x: area.x,
y: area.y,
width: area.width,
height: max_transcript_height,
};
let (transcript_area, _) = split_transcript_area(transcript_full_area);

self.transcript_view_cache
.ensure_wrapped(cells, transcript_area.width);
let total_lines = self.transcript_view_cache.lines().len();
if total_lines == 0 {
Clear.render_ref(transcript_area, frame.buffer);
Clear.render_ref(transcript_full_area, frame.buffer);
self.transcript_scroll = TranscriptScroll::default();
self.transcript_view_top = 0;
self.transcript_total_lines = 0;
Expand Down Expand Up @@ -760,12 +768,14 @@ impl App {
);
}

let transcript_area = Rect {
let transcript_full_area = Rect {
x: area.x,
y: area.y,
width: area.width,
height: transcript_visible_height,
};
let (transcript_area, transcript_scrollbar_area) =
split_transcript_area(transcript_full_area);

// Cache a few viewports worth of rasterized rows so redraws during streaming can cheaply
// copy already-rendered `Cell`s instead of re-running grapheme segmentation.
Expand Down Expand Up @@ -806,6 +816,13 @@ impl App {
} else {
self.transcript_copy_ui.clear_affordance();
}
render_transcript_scrollbar_if_active(
frame.buffer,
transcript_scrollbar_area,
total_lines,
max_visible,
top_offset,
);
chat_top
}

Expand Down Expand Up @@ -854,21 +871,45 @@ impl App {
return;
}

let transcript_area = Rect {
let transcript_full_area = Rect {
x: 0,
y: 0,
width,
height: transcript_height,
};
let (transcript_area, transcript_scrollbar_area) =
split_transcript_area(transcript_full_area);
let base_x = transcript_area.x.saturating_add(TRANSCRIPT_GUTTER_COLS);
let max_x = transcript_area.right().saturating_sub(1);

if matches!(
self.transcript_scrollbar_ui
.handle_mouse_event(TranscriptScrollbarMouseEvent {
tui,
mouse_event,
transcript_area,
scrollbar_area: transcript_scrollbar_area,
transcript_cells: &self.transcript_cells,
transcript_view_cache: &mut self.transcript_view_cache,
transcript_scroll: &mut self.transcript_scroll,
transcript_view_top: &mut self.transcript_view_top,
transcript_total_lines: &mut self.transcript_total_lines,
mouse_scroll_state: &mut self.scroll_state,
}),
TranscriptScrollbarMouseHandling::Handled
) {
return;
}

// Treat the transcript as the only interactive region for transcript selection.
//
// This prevents clicks in the composer/footer from starting or extending a transcript
// selection, while still allowing a left-click outside the transcript to clear an
// existing highlight.
if mouse_event.row < transcript_area.y || mouse_event.row >= transcript_area.bottom() {
if !self.transcript_scrollbar_ui.pointer_capture_active()
&& (mouse_event.row < transcript_full_area.y
|| mouse_event.row >= transcript_full_area.bottom())
{
if matches!(
mouse_event.kind,
MouseEventKind::Down(MouseButton::Left) | MouseEventKind::Up(MouseButton::Left)
Expand Down Expand Up @@ -1082,7 +1123,15 @@ impl App {
return None;
}

Some((transcript_height as usize, width))
let transcript_full_area = Rect {
x: 0,
y: 0,
width,
height: transcript_height,
};
let (transcript_area, _) = split_transcript_area(transcript_full_area);

Some((transcript_height as usize, transcript_area.width))
}

/// Scroll the transcript by a number of visual lines.
Expand Down Expand Up @@ -2084,6 +2133,7 @@ mod tests {
CopySelectionShortcut::CtrlShiftC,
),
transcript_copy_action: TranscriptCopyAction::default(),
transcript_scrollbar_ui: TranscriptScrollbarUi::default(),
overlay: None,
deferred_history_lines: Vec::new(),
has_emitted_history_lines: false,
Expand Down Expand Up @@ -2136,6 +2186,7 @@ mod tests {
CopySelectionShortcut::CtrlShiftC,
),
transcript_copy_action: TranscriptCopyAction::default(),
transcript_scrollbar_ui: TranscriptScrollbarUi::default(),
overlay: None,
deferred_history_lines: Vec::new(),
has_emitted_history_lines: false,
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/tui2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ mod transcript_copy_action;
mod transcript_copy_ui;
mod transcript_multi_click;
mod transcript_render;
mod transcript_scrollbar;
mod transcript_scrollbar_ui;
mod transcript_selection;
mod transcript_view_cache;
mod tui;
Expand Down
12 changes: 11 additions & 1 deletion codex-rs/tui2/src/transcript_copy_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ use std::time::Duration;
use std::time::Instant;

use crate::history_cell::HistoryCell;
use crate::transcript_scrollbar::split_transcript_area;
use crate::transcript_selection::TranscriptSelection;
use crate::tui;
use ratatui::layout::Rect;

/// User-visible feedback shown briefly after a copy attempt.
///
Expand Down Expand Up @@ -162,10 +164,18 @@ pub(crate) fn copy_transcript_selection(
return CopySelectionOutcome::NoSelection;
}

let transcript_full_area = Rect {
x: 0,
y: 0,
width,
height: transcript_height,
};
let (transcript_area, _) = split_transcript_area(transcript_full_area);

let Some(text) = crate::transcript_copy::selection_to_copy_text_for_cells(
transcript_cells,
transcript_selection,
width,
transcript_area.width,
) else {
return CopySelectionOutcome::NoSelection;
};
Expand Down
Loading
Loading