Align user history message prefix width (#3467)
<img width="798" height="340" alt="image" src="https://github.com/user-attachments/assets/fdd63f40-9c94-4e3a-bce5-2d2f333a384f" />
This commit is contained in:
@@ -38,6 +38,7 @@ use crate::bottom_pane::textarea::TextAreaState;
|
|||||||
use crate::clipboard_paste::normalize_pasted_path;
|
use crate::clipboard_paste::normalize_pasted_path;
|
||||||
use crate::clipboard_paste::pasted_image_format;
|
use crate::clipboard_paste::pasted_image_format;
|
||||||
use crate::key_hint;
|
use crate::key_hint;
|
||||||
|
use crate::ui_consts::LIVE_PREFIX_COLS;
|
||||||
use codex_file_search::FileMatch;
|
use codex_file_search::FileMatch;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -136,7 +137,9 @@ impl ChatComposer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn desired_height(&self, width: u16) -> u16 {
|
pub fn desired_height(&self, width: u16) -> u16 {
|
||||||
self.textarea.desired_height(width - 1)
|
// Leave 1 column for the left border and 1 column for left padding
|
||||||
|
self.textarea
|
||||||
|
.desired_height(width.saturating_sub(LIVE_PREFIX_COLS))
|
||||||
+ match &self.active_popup {
|
+ match &self.active_popup {
|
||||||
ActivePopup::None => FOOTER_HEIGHT_WITH_HINT,
|
ActivePopup::None => FOOTER_HEIGHT_WITH_HINT,
|
||||||
ActivePopup::Command(c) => c.calculate_required_height(),
|
ActivePopup::Command(c) => c.calculate_required_height(),
|
||||||
@@ -153,8 +156,9 @@ impl ChatComposer {
|
|||||||
let [textarea_rect, _] =
|
let [textarea_rect, _] =
|
||||||
Layout::vertical([Constraint::Min(1), popup_constraint]).areas(area);
|
Layout::vertical([Constraint::Min(1), popup_constraint]).areas(area);
|
||||||
let mut textarea_rect = textarea_rect;
|
let mut textarea_rect = textarea_rect;
|
||||||
textarea_rect.width = textarea_rect.width.saturating_sub(1);
|
// Leave 1 for border and 1 for padding
|
||||||
textarea_rect.x += 1;
|
textarea_rect.width = textarea_rect.width.saturating_sub(LIVE_PREFIX_COLS);
|
||||||
|
textarea_rect.x = textarea_rect.x.saturating_add(LIVE_PREFIX_COLS);
|
||||||
let state = self.textarea_state.borrow();
|
let state = self.textarea_state.borrow();
|
||||||
self.textarea.cursor_pos_with_state(textarea_rect, &state)
|
self.textarea.cursor_pos_with_state(textarea_rect, &state)
|
||||||
}
|
}
|
||||||
@@ -1274,7 +1278,6 @@ impl WidgetRef for ChatComposer {
|
|||||||
key_hint::ctrl('J')
|
key_hint::ctrl('J')
|
||||||
};
|
};
|
||||||
vec![
|
vec![
|
||||||
" ".into(),
|
|
||||||
key_hint::plain('⏎'),
|
key_hint::plain('⏎'),
|
||||||
" send ".into(),
|
" send ".into(),
|
||||||
newline_hint_key,
|
newline_hint_key,
|
||||||
@@ -1342,15 +1345,16 @@ impl WidgetRef for ChatComposer {
|
|||||||
buf,
|
buf,
|
||||||
);
|
);
|
||||||
let mut textarea_rect = textarea_rect;
|
let mut textarea_rect = textarea_rect;
|
||||||
textarea_rect.width = textarea_rect.width.saturating_sub(1);
|
// Leave 1 for border and 1 for padding
|
||||||
textarea_rect.x += 1;
|
textarea_rect.width = textarea_rect.width.saturating_sub(LIVE_PREFIX_COLS);
|
||||||
|
textarea_rect.x = textarea_rect.x.saturating_add(LIVE_PREFIX_COLS);
|
||||||
|
|
||||||
let mut state = self.textarea_state.borrow_mut();
|
let mut state = self.textarea_state.borrow_mut();
|
||||||
StatefulWidgetRef::render_ref(&(&self.textarea), textarea_rect, buf, &mut state);
|
StatefulWidgetRef::render_ref(&(&self.textarea), textarea_rect, buf, &mut state);
|
||||||
if self.textarea.text().is_empty() {
|
if self.textarea.text().is_empty() {
|
||||||
Line::from(self.placeholder_text.as_str())
|
Line::from(self.placeholder_text.as_str())
|
||||||
.style(Style::default().dim())
|
.style(Style::default().dim())
|
||||||
.render_ref(textarea_rect.inner(Margin::new(1, 0)), buf);
|
.render_ref(textarea_rect.inner(Margin::new(0, 0)), buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
source: tui/src/bottom_pane/chat_composer.rs
|
source: tui/src/bottom_pane/chat_composer.rs
|
||||||
expression: terminal.backend()
|
expression: terminal.backend()
|
||||||
---
|
---
|
||||||
"▌[Pasted Content 1002 chars][Pasted Content 1004 chars] "
|
"▌ [Pasted Content 1002 chars][Pasted Content 1004 chars] "
|
||||||
"▌ "
|
"▌ "
|
||||||
"▌ "
|
"▌ "
|
||||||
"▌ "
|
"▌ "
|
||||||
@@ -11,4 +11,4 @@ expression: terminal.backend()
|
|||||||
"▌ "
|
"▌ "
|
||||||
"▌ "
|
"▌ "
|
||||||
" "
|
" "
|
||||||
" ⏎ send ⌃J newline ⌃T transcript ⌃C quit "
|
"⏎ send ⌃J newline ⌃T transcript ⌃C quit "
|
||||||
|
|||||||
@@ -11,4 +11,4 @@ expression: terminal.backend()
|
|||||||
"▌ "
|
"▌ "
|
||||||
"▌ "
|
"▌ "
|
||||||
" "
|
" "
|
||||||
" ⏎ send ⌃J newline ⌃T transcript ⌃C quit "
|
"⏎ send ⌃J newline ⌃T transcript ⌃C quit "
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
source: tui/src/bottom_pane/chat_composer.rs
|
source: tui/src/bottom_pane/chat_composer.rs
|
||||||
expression: terminal.backend()
|
expression: terminal.backend()
|
||||||
---
|
---
|
||||||
"▌[Pasted Content 1005 chars] "
|
"▌ [Pasted Content 1005 chars] "
|
||||||
"▌ "
|
"▌ "
|
||||||
"▌ "
|
"▌ "
|
||||||
"▌ "
|
"▌ "
|
||||||
@@ -11,4 +11,4 @@ expression: terminal.backend()
|
|||||||
"▌ "
|
"▌ "
|
||||||
"▌ "
|
"▌ "
|
||||||
" "
|
" "
|
||||||
" ⏎ send ⌃J newline ⌃T transcript ⌃C quit "
|
"⏎ send ⌃J newline ⌃T transcript ⌃C quit "
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
source: tui/src/bottom_pane/chat_composer.rs
|
source: tui/src/bottom_pane/chat_composer.rs
|
||||||
expression: terminal.backend()
|
expression: terminal.backend()
|
||||||
---
|
---
|
||||||
"▌[Pasted Content 1003 chars][Pasted Content 1007 chars] another short paste "
|
"▌ [Pasted Content 1003 chars][Pasted Content 1007 chars] another short paste "
|
||||||
"▌ "
|
"▌ "
|
||||||
"▌ "
|
"▌ "
|
||||||
"▌ "
|
"▌ "
|
||||||
@@ -11,4 +11,4 @@ expression: terminal.backend()
|
|||||||
"▌ "
|
"▌ "
|
||||||
"▌ "
|
"▌ "
|
||||||
" "
|
" "
|
||||||
" ⏎ send ⌃J newline ⌃T transcript ⌃C quit "
|
"⏎ send ⌃J newline ⌃T transcript ⌃C quit "
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
source: tui/src/bottom_pane/chat_composer.rs
|
source: tui/src/bottom_pane/chat_composer.rs
|
||||||
expression: terminal.backend()
|
expression: terminal.backend()
|
||||||
---
|
---
|
||||||
"▌/mo "
|
"▌ /mo "
|
||||||
"▌ "
|
"▌ "
|
||||||
"▌/model choose what model and reasoning effort to use "
|
"▌/model choose what model and reasoning effort to use "
|
||||||
"▌/mention mention a file "
|
"▌/mention mention a file "
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
source: tui/src/bottom_pane/chat_composer.rs
|
source: tui/src/bottom_pane/chat_composer.rs
|
||||||
expression: terminal.backend()
|
expression: terminal.backend()
|
||||||
---
|
---
|
||||||
"▌short "
|
"▌ short "
|
||||||
"▌ "
|
"▌ "
|
||||||
"▌ "
|
"▌ "
|
||||||
"▌ "
|
"▌ "
|
||||||
@@ -11,4 +11,4 @@ expression: terminal.backend()
|
|||||||
"▌ "
|
"▌ "
|
||||||
"▌ "
|
"▌ "
|
||||||
" "
|
" "
|
||||||
" ⏎ send ⌃J newline ⌃T transcript ⌃C quit "
|
"⏎ send ⌃J newline ⌃T transcript ⌃C quit "
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
source: tui/src/chatwidget/tests.rs
|
source: tui/src/chatwidget/tests.rs
|
||||||
expression: terminal.backend()
|
expression: terminal.backend()
|
||||||
---
|
---
|
||||||
" Thinking (0s • Esc to interrupt) "
|
" Thinking (0s • Esc to interrupt) "
|
||||||
"▌ Ask Codex to do anything "
|
"▌ Ask Codex to do anything "
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ expression: visual
|
|||||||
└ Search Change Approved
|
└ Search Change Approved
|
||||||
Read diff_render.rs
|
Read diff_render.rs
|
||||||
|
|
||||||
Investigating rendering code (0s • Esc to interrupt)
|
Investigating rendering code (0s • Esc to interrupt)
|
||||||
|
|
||||||
▌Summarize recent commits
|
▌ Summarize recent commits
|
||||||
|
|
||||||
⏎ send ⌃J newline ⌃T transcript ⌃C quit
|
⏎ send ⌃J newline ⌃T transcript ⌃C quit
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ source: tui/src/chatwidget/tests.rs
|
|||||||
expression: terminal.backend()
|
expression: terminal.backend()
|
||||||
---
|
---
|
||||||
" "
|
" "
|
||||||
" Analyzing (0s • Esc to interrupt) "
|
" Analyzing (0s • Esc to interrupt) "
|
||||||
" "
|
" "
|
||||||
"▌ Ask Codex to do anything "
|
"▌ Ask Codex to do anything "
|
||||||
" "
|
" "
|
||||||
" ⏎ send ⌃J newline ⌃T transcript ⌃C quit "
|
"⏎ send ⌃J newline ⌃T transcript ⌃C quit "
|
||||||
" "
|
" "
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use crate::render::line_utils::prefix_lines;
|
|||||||
use crate::render::line_utils::push_owned_lines;
|
use crate::render::line_utils::push_owned_lines;
|
||||||
use crate::slash_command::SlashCommand;
|
use crate::slash_command::SlashCommand;
|
||||||
use crate::text_formatting::format_and_truncate_tool_result;
|
use crate::text_formatting::format_and_truncate_tool_result;
|
||||||
|
use crate::ui_consts::LIVE_PREFIX_COLS;
|
||||||
use crate::wrapping::RtOptions;
|
use crate::wrapping::RtOptions;
|
||||||
use crate::wrapping::word_wrap_line;
|
use crate::wrapping::word_wrap_line;
|
||||||
use crate::wrapping::word_wrap_lines;
|
use crate::wrapping::word_wrap_lines;
|
||||||
@@ -106,7 +107,7 @@ impl HistoryCell for UserHistoryCell {
|
|||||||
let mut lines: Vec<Line<'static>> = Vec::new();
|
let mut lines: Vec<Line<'static>> = Vec::new();
|
||||||
|
|
||||||
// Wrap the content first, then prefix each wrapped line with the marker.
|
// Wrap the content first, then prefix each wrapped line with the marker.
|
||||||
let wrap_width = width.saturating_sub(1); // account for the ▌ prefix
|
let wrap_width = width.saturating_sub(LIVE_PREFIX_COLS); // account for the ▌ prefix and trailing space
|
||||||
let wrapped = textwrap::wrap(
|
let wrapped = textwrap::wrap(
|
||||||
&self.message,
|
&self.message,
|
||||||
textwrap::Options::new(wrap_width as usize)
|
textwrap::Options::new(wrap_width as usize)
|
||||||
@@ -114,7 +115,7 @@ impl HistoryCell for UserHistoryCell {
|
|||||||
);
|
);
|
||||||
|
|
||||||
for line in wrapped {
|
for line in wrapped {
|
||||||
lines.push(vec!["▌".cyan().dim(), line.to_string().dim()].into());
|
lines.push(vec!["▌ ".cyan().dim(), line.to_string().dim()].into());
|
||||||
}
|
}
|
||||||
lines
|
lines
|
||||||
}
|
}
|
||||||
@@ -644,25 +645,25 @@ pub(crate) fn new_session_info(
|
|||||||
]));
|
]));
|
||||||
lines.push(Line::from("".dim()));
|
lines.push(Line::from("".dim()));
|
||||||
lines.push(Line::from(
|
lines.push(Line::from(
|
||||||
" To get started, describe a task or try one of these commands:".dim(),
|
" To get started, describe a task or try one of these commands:".dim(),
|
||||||
));
|
));
|
||||||
lines.push(Line::from("".dim()));
|
lines.push(Line::from("".dim()));
|
||||||
if !has_agents_md {
|
if !has_agents_md {
|
||||||
lines.push(Line::from(vec![
|
lines.push(Line::from(vec![
|
||||||
" /init".bold(),
|
" /init".bold(),
|
||||||
format!(" - {}", SlashCommand::Init.description()).dim(),
|
format!(" - {}", SlashCommand::Init.description()).dim(),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
lines.push(Line::from(vec![
|
lines.push(Line::from(vec![
|
||||||
" /status".bold(),
|
" /status".bold(),
|
||||||
format!(" - {}", SlashCommand::Status.description()).dim(),
|
format!(" - {}", SlashCommand::Status.description()).dim(),
|
||||||
]));
|
]));
|
||||||
lines.push(Line::from(vec![
|
lines.push(Line::from(vec![
|
||||||
" /approvals".bold(),
|
" /approvals".bold(),
|
||||||
format!(" - {}", SlashCommand::Approvals.description()).dim(),
|
format!(" - {}", SlashCommand::Approvals.description()).dim(),
|
||||||
]));
|
]));
|
||||||
lines.push(Line::from(vec![
|
lines.push(Line::from(vec![
|
||||||
" /model".bold(),
|
" /model".bold(),
|
||||||
format!(" - {}", SlashCommand::Model.description()).dim(),
|
format!(" - {}", SlashCommand::Model.description()).dim(),
|
||||||
]));
|
]));
|
||||||
PlainHistoryCell { lines }
|
PlainHistoryCell { lines }
|
||||||
@@ -1785,7 +1786,7 @@ mod tests {
|
|||||||
message: msg.to_string(),
|
message: msg.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Small width to force wrapping more clearly. Effective wrap width is width-1 due to the ▌ prefix.
|
// Small width to force wrapping more clearly. Effective wrap width is width-2 due to the ▌ prefix and trailing space.
|
||||||
let width: u16 = 12;
|
let width: u16 = 12;
|
||||||
let lines = cell.display_lines(width);
|
let lines = cell.display_lines(width);
|
||||||
let rendered = render_lines(&lines).join("\n");
|
let rendered = render_lines(&lines).join("\n");
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ mod status_indicator_widget;
|
|||||||
mod streaming;
|
mod streaming;
|
||||||
mod text_formatting;
|
mod text_formatting;
|
||||||
mod tui;
|
mod tui;
|
||||||
|
mod ui_consts;
|
||||||
mod user_approval_widget;
|
mod user_approval_widget;
|
||||||
mod version;
|
mod version;
|
||||||
mod wrapping;
|
mod wrapping;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
source: tui/src/history_cell.rs
|
source: tui/src/history_cell.rs
|
||||||
expression: rendered
|
expression: rendered
|
||||||
---
|
---
|
||||||
▌one two
|
▌ one two
|
||||||
▌three four
|
▌ three four
|
||||||
▌five six
|
▌ five six
|
||||||
▌seven
|
▌ seven
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
source: tui/src/status_indicator_widget.rs
|
source: tui/src/status_indicator_widget.rs
|
||||||
expression: terminal.backend()
|
expression: terminal.backend()
|
||||||
---
|
---
|
||||||
" Working (0s • Esc t"
|
" Working (0s • Esc "
|
||||||
" "
|
" "
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
source: tui/src/status_indicator_widget.rs
|
source: tui/src/status_indicator_widget.rs
|
||||||
expression: terminal.backend()
|
expression: terminal.backend()
|
||||||
---
|
---
|
||||||
" Working (0s • Esc to interrupt) "
|
" Working (0s • Esc to interrupt) "
|
||||||
" "
|
" "
|
||||||
" ↳ first "
|
" ↳ first "
|
||||||
" ↳ second "
|
" ↳ second "
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
source: tui/src/status_indicator_widget.rs
|
source: tui/src/status_indicator_widget.rs
|
||||||
expression: terminal.backend()
|
expression: terminal.backend()
|
||||||
---
|
---
|
||||||
" Working (0s • Esc to interrupt) "
|
" Working (0s • Esc to interrupt) "
|
||||||
" "
|
" "
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use crate::app_event_sender::AppEventSender;
|
|||||||
use crate::key_hint;
|
use crate::key_hint;
|
||||||
use crate::shimmer::shimmer_spans;
|
use crate::shimmer::shimmer_spans;
|
||||||
use crate::tui::FrameRequester;
|
use crate::tui::FrameRequester;
|
||||||
|
use crate::ui_consts::LIVE_PREFIX_COLS;
|
||||||
|
|
||||||
pub(crate) struct StatusIndicatorWidget {
|
pub(crate) struct StatusIndicatorWidget {
|
||||||
/// Animated header text (defaults to "Working").
|
/// Animated header text (defaults to "Working").
|
||||||
@@ -159,7 +160,7 @@ impl WidgetRef for StatusIndicatorWidget {
|
|||||||
let pretty_elapsed = fmt_elapsed_compact(elapsed);
|
let pretty_elapsed = fmt_elapsed_compact(elapsed);
|
||||||
|
|
||||||
// Plain rendering: no borders or padding so the live cell is visually indistinguishable from terminal scrollback.
|
// Plain rendering: no borders or padding so the live cell is visually indistinguishable from terminal scrollback.
|
||||||
let mut spans = vec![" ".into()];
|
let mut spans = vec![" ".repeat(LIVE_PREFIX_COLS as usize).into()];
|
||||||
spans.extend(shimmer_spans(&self.header));
|
spans.extend(shimmer_spans(&self.header));
|
||||||
spans.extend(vec![
|
spans.extend(vec![
|
||||||
" ".into(),
|
" ".into(),
|
||||||
|
|||||||
10
codex-rs/tui/src/ui_consts.rs
Normal file
10
codex-rs/tui/src/ui_consts.rs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
//! Shared UI constants for layout and alignment within the TUI.
|
||||||
|
|
||||||
|
/// Width (in terminal columns) reserved for the left gutter/prefix used by
|
||||||
|
/// live cells and aligned widgets.
|
||||||
|
///
|
||||||
|
/// Semantics:
|
||||||
|
/// - Chat composer reserves this many columns for the left border + padding.
|
||||||
|
/// - Status indicator lines begin with this many spaces for alignment.
|
||||||
|
/// - User history lines account for this many columns (e.g., "▌ ") when wrapping.
|
||||||
|
pub(crate) const LIVE_PREFIX_COLS: u16 = 2;
|
||||||
Reference in New Issue
Block a user