use super::schema::{Category, EditorKind, SettingDef};
macro_rules! define_settings {
($(
$key:ident, $label:expr, $category:ident, $editor:expr, $help:expr,
$apply:expr;
)*) => {
pub(crate) const ALL_SETTINGS: &[SettingDef] = &[
$(
SettingDef {
key: stringify!($key),
label: $label,
category: Category::$category,
editor: $editor,
help: $help,
},
)*
];
pub(crate) fn apply_setting_dispatch(
key: &str,
value: &str,
runtime: &mut synaps_cli::Runtime,
app: &mut crate::chatui::app::App,
) {
match key {
$(
stringify!($key) => {
let handler: fn(&mut synaps_cli::Runtime, &mut crate::chatui::app::App, &str) = $apply;
handler(runtime, app, value);
}
)*
_ => {}
}
}
};
}
define_settings! {
model, "Model", Model, EditorKind::ModelPicker,
"Which Claude model to use.",
|runtime, _app, value| { runtime.set_model(value.to_string()); };
thinking, "Thinking", Model,
EditorKind::Cycler(&["low", "medium", "high", "xhigh", "adaptive"]),
"Thinking depth — controls effort on adaptive models, budget on legacy.",
|runtime, _app, value| {
let budget = match value {
"low" => 2048,
"medium" => 4096,
"high" => 16384,
"xhigh" => 32768,
"adaptive" => 0,
_ => return,
};
runtime.set_thinking_budget(budget);
};
context_window, "Context window", Model,
EditorKind::Cycler(&["200k", "1m", "auto"]),
"Override context window limit (auto = model default).",
|runtime, app, value| {
let window = match value {
"200k" | "200K" => Some(200_000u64),
"1m" | "1M" => Some(1_000_000u64),
"auto" => None,
_ => return,
};
runtime.set_context_window(window);
app.last_turn_context_window = runtime.context_window();
};
compaction_model, "Compaction model", Model,
EditorKind::ModelPicker,
"Model used for /compact (default: claude-sonnet-4-6).",
|runtime, _app, value| {
let model = if value.is_empty() || value == "auto" || value == "default" {
None
} else {
Some(value.to_string())
};
runtime.set_compaction_model(model);
};
api_retries, "API retries", Agent, EditorKind::Text { numeric: true },
"Retries on transient API errors.",
|runtime, _app, value| {
if let Ok(n) = value.parse::<u32>() { runtime.set_api_retries(n); }
};
subagent_timeout, "Subagent timeout", Agent, EditorKind::Text { numeric: true },
"Seconds before a dispatched subagent is canceled.",
|runtime, _app, value| {
if let Ok(n) = value.parse::<u64>() { runtime.set_subagent_timeout(n); }
};
max_tool_output, "Max tool output", ToolLimits, EditorKind::Text { numeric: true },
"Bytes to capture from a tool before truncating.",
|runtime, _app, value| {
if let Ok(n) = value.parse::<usize>() { runtime.set_max_tool_output(n); }
};
bash_timeout, "Bash timeout", ToolLimits, EditorKind::Text { numeric: true },
"Default seconds allowed for a bash command.",
|runtime, _app, value| {
if let Ok(n) = value.parse::<u64>() { runtime.set_bash_timeout(n); }
};
bash_max_timeout, "Bash max timeout", ToolLimits, EditorKind::Text { numeric: true },
"Legacy setting retained for config compatibility; requested bash timeouts are no longer clamped.",
|runtime, _app, value| {
if let Ok(n) = value.parse::<u64>() { runtime.set_bash_max_timeout(n); }
};
theme, "Theme", Appearance, EditorKind::ThemePicker,
"Color theme (restart required).",
|_runtime, _app, _value| { };
sidecar_toggle_key, "Sidecar toggle key", Sidecar,
EditorKind::Cycler(&["F8", "F2", "F12", "C-V", "C-G"]),
"Keybind that toggles the active sidecar plugin. Takes effect immediately.",
|_runtime, app, value| {
if let Some(kb) = app.keybinds.as_ref() {
match kb.write() {
Ok(mut g) => {
if let Err(e) = g.set_slash_command_key("sidecar toggle", value) {
tracing::warn!("sidecar_toggle_key apply failed: {}", e);
}
}
Err(_) => tracing::warn!("sidecar_toggle_key apply: registry poisoned"),
}
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sidecar_toggle_key_setting_is_in_sidecar_category() {
let def = ALL_SETTINGS
.iter()
.find(|d| d.key == "sidecar_toggle_key")
.expect("sidecar_toggle_key setting should be defined");
assert_eq!(def.category, Category::Sidecar);
}
#[test]
fn sidecar_toggle_key_static_setting_still_defined_for_backward_compat() {
let def = ALL_SETTINGS
.iter()
.find(|d| d.key == "sidecar_toggle_key")
.expect("sidecar_toggle_key setting must remain in ALL_SETTINGS for back-compat");
assert_eq!(def.category, Category::Sidecar);
match def.editor {
EditorKind::Cycler(opts) => {
assert!(opts.contains(&"F8"));
assert!(opts.contains(&"C-V"));
}
_ => panic!("expected Cycler editor for sidecar_toggle_key"),
}
}
}