Skip to main content

codetether_agent/tui/
settings.rs

1use 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}