codetether_agent/tui/
settings.rs1use ratatui::{
2 Frame,
3 layout::{Constraint, Direction, Layout, Rect},
4 style::{Color, Style, Stylize},
5 text::{Line, Span},
6 widgets::{Block, Borders, Paragraph, Wrap},
7};
8
9use crate::tui::app::state::AppState;
10use crate::tui::status::render_status;
11
12fn setting_value_span(enabled: bool) -> Span<'static> {
13 if enabled {
14 "ON".green().into()
15 } else {
16 "OFF".yellow().into()
17 }
18}
19
20fn setting_line(label: &'static str, enabled: bool, selected: bool) -> Line<'static> {
21 let prefix = if selected { "> " } else { " " };
22 let label_style = if selected {
23 Style::default().fg(Color::Cyan).bold()
24 } else {
25 Style::default()
26 };
27
28 Line::from(vec![
29 Span::styled(prefix, label_style),
30 Span::styled(format!("{label}: "), label_style),
31 setting_value_span(enabled),
32 ])
33}
34
35fn settings_lines(app_state: &AppState) -> Vec<Line<'static>> {
36 vec![
37 Line::from("Settings"),
38 Line::from(""),
39 setting_line(
40 "Edit auto-apply",
41 app_state.auto_apply_edits,
42 app_state.selected_settings_index == 0,
43 ),
44 Line::from(" Automatically confirms pending edit/multiedit previews in the TUI."),
45 Line::from(""),
46 setting_line(
47 "Network access",
48 app_state.allow_network,
49 app_state.selected_settings_index == 1,
50 ),
51 Line::from(" Allows sandboxed bash commands in this TUI session to use network access."),
52 Line::from(""),
53 setting_line(
54 "Slash autocomplete",
55 app_state.slash_autocomplete,
56 app_state.selected_settings_index == 2,
57 ),
58 Line::from(" Enables Tab completion for slash commands in the composer."),
59 Line::from(""),
60 setting_line(
61 "Worktree isolation",
62 app_state.use_worktree,
63 app_state.selected_settings_index == 3,
64 ),
65 Line::from(" Runs agent work in a git worktree branch, auto-merged on success."),
66 Line::from(""),
67 Line::from("Controls:"),
68 Line::from(" - Up / Down selects a setting"),
69 Line::from(" - Enter toggles the selected setting"),
70 Line::from(" - A toggles edit auto-apply"),
71 Line::from(" - N toggles network access"),
72 Line::from(" - Tab toggles slash autocomplete"),
73 Line::from(" - /autoapply on|off|toggle|status works from chat"),
74 Line::from(" - /settings opens this panel"),
75 Line::from(" - Esc returns to chat"),
76 ]
77}
78
79pub fn render_settings(f: &mut Frame, area: Rect, app_state: &AppState) {
80 let chunks = Layout::default()
81 .direction(Direction::Vertical)
82 .constraints([Constraint::Min(8), Constraint::Length(3)])
83 .split(area);
84
85 let widget = Paragraph::new(settings_lines(app_state))
86 .block(Block::default().borders(Borders::ALL).title("Settings"))
87 .wrap(Wrap { trim: false });
88 f.render_widget(widget, chunks[0]);
89 render_status(f, chunks[1], &app_state.status);
90}
91
92#[cfg(test)]
93mod tests {
94 use super::settings_lines;
95 use crate::tui::app::state::AppState;
96
97 #[test]
98 fn settings_panel_mentions_adjustable_controls() {
99 let text = settings_lines(&AppState::default())
100 .into_iter()
101 .map(|line| {
102 line.spans
103 .into_iter()
104 .map(|span| span.content.into_owned())
105 .collect::<String>()
106 })
107 .collect::<Vec<_>>()
108 .join("\n");
109
110 assert!(text.contains("Edit auto-apply"));
111 assert!(text.contains("Network access"));
112 assert!(text.contains("Slash autocomplete"));
113 assert!(text.contains("Up / Down selects a setting"));
114 assert!(text.contains("/autoapply on|off|toggle|status"));
115 }
116}