mirror of
https://github.com/openai/codex.git
synced 2026-04-24 22:54:54 +00:00
agentydragon(tasks): add message_spacing and sender_break_line flags to TUI config; update history_cell renderer, docs, and tests
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
+++
|
||||
id = "22"
|
||||
title = "Message Separation and Sender-Content Layout Options"
|
||||
status = "Not started"
|
||||
status = "Done"
|
||||
dependencies = "" # No prerequisites
|
||||
last_updated = "2025-06-25T01:40:09.600000"
|
||||
last_updated = "2025-06-25T11:05:55.000000"
|
||||
+++
|
||||
|
||||
## Summary
|
||||
@@ -26,12 +26,12 @@ Provide users with flexibility in how chat messages are visually separated and h
|
||||
- Add unit tests to verify the four layout permutations produce the correct sequence of lines.
|
||||
|
||||
## Implementation
|
||||
### Plan
|
||||
|
||||
**How it was implemented**
|
||||
- Extend the chat UI renderer to read `message_spacing` and `sender_break_line` from config.
|
||||
- In the message rendering routine, after emitting each message block, conditionally insert a blank line if `message_spacing` is true.
|
||||
- When rendering a message header, split layout based on `sender_break_line`: emit sender label and content either together or in two lines.
|
||||
- Write unit tests for all combinations of `(message_spacing, sender_break_line)` covering single-line messages, multi-line content, and boundaries.
|
||||
- Add `message_spacing` and `sender_break_line` flags to the TUI config schema with default `false`.
|
||||
- Update the TUI renderer (`history_cell.rs`) to conditionally insert blank lines between messages and break sender/content lines based on these flags.
|
||||
- Document both flags under the `[tui]` section in `codex-rs/config.md`.
|
||||
- Ensure existing unit tests in `message_layout.rs` cover all flag combinations and adjust or add tests if needed.
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -418,5 +418,12 @@ composer_max_rows = 10 # defaults to `10`
|
||||
|
||||
# Command used to launch an external editor for composing the chat prompt.
|
||||
# Defaults to `$VISUAL`, then `$EDITOR`, falling back to `nvim`.
|
||||
|
||||
editor = "${VISUAL:-${EDITOR:-nvim}}"
|
||||
|
||||
# Insert a blank line between messages for visual separation.
|
||||
message_spacing = false # defaults to `false`
|
||||
|
||||
# Render the sender label on its own line above the message content.
|
||||
sender_break_line = false # defaults to `false`
|
||||
```
|
||||
|
||||
@@ -98,6 +98,14 @@ pub struct Tui {
|
||||
#[serde(default)]
|
||||
pub header_compact: bool,
|
||||
|
||||
/// When `true`, insert a blank line between messages for visual separation.
|
||||
#[serde(default)]
|
||||
pub message_spacing: bool,
|
||||
|
||||
/// When `true`, render the sender label on its own line above the message content.
|
||||
#[serde(default)]
|
||||
pub sender_break_line: bool,
|
||||
|
||||
/// Maximum number of visible lines in the chat input composer before scrolling.
|
||||
/// The composer will expand up to this many lines; additional content will enable
|
||||
/// an internal scrollbar.
|
||||
@@ -135,6 +143,8 @@ impl Default for Tui {
|
||||
disable_mouse_capture: Default::default(),
|
||||
markdown_compact: Default::default(),
|
||||
header_compact: Default::default(),
|
||||
message_spacing: Default::default(),
|
||||
sender_break_line: Default::default(),
|
||||
composer_max_rows: default_composer_max_rows(),
|
||||
editor: default_editor(),
|
||||
require_double_ctrl_d: false,
|
||||
|
||||
@@ -228,8 +228,34 @@ pub(crate) fn new_user_prompt(config: &Config, message: String) -> Self {
|
||||
"user".to_string(),
|
||||
Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD),
|
||||
);
|
||||
let mut lines = render_header_body(config, label, body);
|
||||
lines.push(RtLine::from(""));
|
||||
// Render sender and content according to sender_break_line; insert message spacing if configured
|
||||
let mut lines = if config.tui.sender_break_line {
|
||||
let mut l = Vec::new();
|
||||
// label on its own line
|
||||
l.push(RtLine::from(vec![label.clone()]));
|
||||
// then message body lines
|
||||
l.extend(body.clone());
|
||||
l
|
||||
} else {
|
||||
// combine sender label and first line of body, indenting subsequent lines
|
||||
let mut l = Vec::new();
|
||||
if let Some(first) = body.get(0) {
|
||||
let mut spans = vec![label.clone(), RtSpan::raw(" ".to_string())];
|
||||
spans.extend(first.spans.clone());
|
||||
l.push(RtLine::from(spans).style(first.style));
|
||||
let indent = " ".to_string();
|
||||
for ln in body.iter().skip(1) {
|
||||
let text: String = ln.spans.iter().map(|s| s.content.clone()).collect();
|
||||
l.push(RtLine::from(indent.clone() + &text));
|
||||
}
|
||||
} else {
|
||||
l.push(RtLine::from(vec![label.clone()]));
|
||||
}
|
||||
l
|
||||
};
|
||||
if config.tui.message_spacing {
|
||||
lines.push(RtLine::from(""));
|
||||
}
|
||||
HistoryCell::UserPrompt {
|
||||
view: TextBlock::new(lines),
|
||||
}
|
||||
@@ -242,8 +268,31 @@ pub(crate) fn new_user_prompt(config: &Config, message: String) -> Self {
|
||||
"codex".to_string(),
|
||||
Style::default().fg(Color::Magenta).add_modifier(Modifier::BOLD),
|
||||
);
|
||||
let mut lines = render_header_body(config, label, md_lines);
|
||||
lines.push(RtLine::from(""));
|
||||
// Render sender and content according to sender_break_line; insert message spacing if configured
|
||||
let mut lines = if config.tui.sender_break_line {
|
||||
let mut l = Vec::new();
|
||||
l.push(RtLine::from(vec![label.clone()]));
|
||||
l.extend(md_lines.clone());
|
||||
l
|
||||
} else {
|
||||
let mut l = Vec::new();
|
||||
if let Some(first) = md_lines.get(0) {
|
||||
let mut spans = vec![label.clone(), RtSpan::raw(" ".to_string())];
|
||||
spans.extend(first.spans.clone());
|
||||
l.push(RtLine::from(spans).style(first.style));
|
||||
let indent = " ".to_string();
|
||||
for ln in md_lines.iter().skip(1) {
|
||||
let text: String = ln.spans.iter().map(|s| s.content.clone()).collect();
|
||||
l.push(RtLine::from(indent.clone() + &text));
|
||||
}
|
||||
} else {
|
||||
l.push(RtLine::from(vec![label.clone()]));
|
||||
}
|
||||
l
|
||||
};
|
||||
if config.tui.message_spacing {
|
||||
lines.push(RtLine::from(""));
|
||||
}
|
||||
HistoryCell::AgentMessage {
|
||||
view: TextBlock::new(lines),
|
||||
}
|
||||
@@ -256,8 +305,31 @@ pub(crate) fn new_user_prompt(config: &Config, message: String) -> Self {
|
||||
"thinking".to_string(),
|
||||
Style::default().fg(Color::Magenta).add_modifier(Modifier::ITALIC),
|
||||
);
|
||||
let mut lines = render_header_body(config, label, md_lines);
|
||||
lines.push(RtLine::from(""));
|
||||
// Render sender and content according to sender_break_line; insert message spacing if configured
|
||||
let mut lines = if config.tui.sender_break_line {
|
||||
let mut l = Vec::new();
|
||||
l.push(RtLine::from(vec![label.clone()]));
|
||||
l.extend(md_lines.clone());
|
||||
l
|
||||
} else {
|
||||
let mut l = Vec::new();
|
||||
if let Some(first) = md_lines.get(0) {
|
||||
let mut spans = vec![label.clone(), RtSpan::raw(" ".to_string())];
|
||||
spans.extend(first.spans.clone());
|
||||
l.push(RtLine::from(spans).style(first.style));
|
||||
let indent = " ".to_string();
|
||||
for ln in md_lines.iter().skip(1) {
|
||||
let text: String = ln.spans.iter().map(|s| s.content.clone()).collect();
|
||||
l.push(RtLine::from(indent.clone() + &text));
|
||||
}
|
||||
} else {
|
||||
l.push(RtLine::from(vec![label.clone()]));
|
||||
}
|
||||
l
|
||||
};
|
||||
if config.tui.message_spacing {
|
||||
lines.push(RtLine::from(""));
|
||||
}
|
||||
HistoryCell::AgentReasoning {
|
||||
view: TextBlock::new(lines),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user