feat(tui): add /statusline command for interactive status line configuration (#10546)

## Summary
- Adds a new `/statusline` command to configure TUI footer status line
- Introduces reusable `MultiSelectPicker` component with keyboard
navigation, optional ordering and toggle support
- Implement status line setup modal that persist configuration to
config.toml

  ## Status Line Items
  The following items can be displayed in the status line:
  - **Model**: Current model name (with optional reasoning level)
  - **Context**: Remaining/used context window percentage
  - **Rate Limits**: 5-day and weekly usage limits
  - **Git**: Current branch (with optimized lookups)
  - **Tokens**: Used tokens, input/output token counts
  - **Session**: Session ID (full or shortened prefix)
  - **Paths**: Current directory, project root
  - **Version**: Codex version

  ## Features
  - Live preview while configuring status line items
  - Fuzzy search filtering in the picker
  - Intelligent truncation when items don't fit
  - Items gracefully omit when data is unavailable
  - Configuration persists to `config.toml`
  - Validates and warns about invalid status line items

  ## Test plan
  - [x] Run `/statusline` and verify picker UI appears
  - [x] Toggle items on/off and verify live preview updates
  - [x] Confirm selection persists after restart
  - [x] Verify truncation behavior with many items selected
  - [x] Test git branch detection in and out of git repos

---------

Co-authored-by: Josh McKinney <joshka@openai.com>
This commit is contained in:
Felipe Coury
2026-02-05 13:50:21 -03:00
committed by GitHub
parent 3b54fd7336
commit b0e5a6305b
25 changed files with 2324 additions and 83 deletions

View File

@@ -327,6 +327,33 @@ pub(crate) fn center_truncate_path(path: &str, max_width: usize) -> String {
front_truncate(path, max_width)
}
/// Join a list of strings with proper English punctuation.
/// Examples:
/// - [] -> ""
/// - ["apple"] -> "apple"
/// - ["apple", "banana"] -> "apple and banana"
/// - ["apple", "banana", "cherry"] -> "apple, banana and cherry"
pub(crate) fn proper_join<T: AsRef<str>>(items: &[T]) -> String {
match items.len() {
0 => String::new(),
1 => items[0].as_ref().to_string(),
2 => format!("{} and {}", items[0].as_ref(), items[1].as_ref()),
_ => {
let last = items[items.len() - 1].as_ref();
let mut result = String::new();
for (i, item) in items.iter().take(items.len() - 1).enumerate() {
if i > 0 {
result.push_str(", ");
}
result.push_str(item.as_ref());
}
format!("{result} and {last}")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -534,4 +561,20 @@ mod tests {
assert_eq!(format_json_compact("null").unwrap(), "null");
assert_eq!(format_json_compact(r#""string""#).unwrap(), r#""string""#);
}
#[test]
fn test_proper_join() {
let empty: Vec<String> = vec![];
assert_eq!(proper_join(&empty), "");
assert_eq!(proper_join(&["apple"]), "apple");
assert_eq!(proper_join(&["apple", "banana"]), "apple and banana");
assert_eq!(
proper_join(&["apple", "banana", "cherry"]),
"apple, banana and cherry"
);
assert_eq!(
proper_join(&["apple", "banana", "cherry", "date"]),
"apple, banana, cherry and date"
);
}
}