use super::SettingsUI;
use crate::input_tab::capture_key_combo;
use crate::section::collapsing_section;
use par_term_config::snippets::SnippetConfig;
use std::collections::HashSet;
pub(super) fn show_snippet_edit_form(
ui: &mut egui::Ui,
settings: &mut SettingsUI,
changes_this_frame: &mut bool,
edit_index: Option<usize>,
collapsed: &mut HashSet<String>,
) {
ui.separator();
ui.horizontal(|ui| {
if ui.button("Save").clicked() {
let snippet = build_snippet_from_form(settings, edit_index);
if let Some(i) = edit_index {
settings.config.snippets[i] = snippet;
} else {
settings.config.snippets.push(snippet);
}
settings.has_changes = true;
*changes_this_frame = true;
settings.snippets_tab.editing_snippet_index = None;
settings.snippets_tab.adding_new_snippet = false;
}
if ui.button("Cancel").clicked() {
settings.snippets_tab.editing_snippet_index = None;
settings.snippets_tab.adding_new_snippet = false;
}
});
ui.separator();
ui.scope(|ui| {
ui.label("Title:");
if ui
.text_edit_singleline(&mut settings.snippets_tab.temp_snippet_title)
.changed()
{
*changes_this_frame = true;
}
ui.label("ID:");
ui.label(
egui::RichText::new(&settings.snippets_tab.temp_snippet_id)
.monospace()
.small(),
);
ui.label("Content:");
if ui
.text_edit_multiline(&mut settings.snippets_tab.temp_snippet_content)
.changed()
{
*changes_this_frame = true;
}
ui.horizontal(|ui| {
if ui
.checkbox(
&mut settings.snippets_tab.temp_snippet_auto_execute,
"Auto-execute",
)
.changed()
{
*changes_this_frame = true;
}
ui.label(
egui::RichText::new("⚡ run immediately")
.small()
.color(egui::Color32::GRAY),
);
});
ui.label("Keybinding:");
ui.horizontal(|ui| {
if settings.snippets_tab.recording_snippet_keybinding {
ui.label(egui::RichText::new("🔴 Recording...").color(egui::Color32::RED));
if let Some(combo) = capture_key_combo(ui) {
settings.snippets_tab.snippet_recorded_combo = Some(combo.clone());
settings.snippets_tab.temp_snippet_keybinding = combo;
settings.snippets_tab.recording_snippet_keybinding = false;
*changes_this_frame = true;
}
} else {
if ui
.text_edit_singleline(&mut settings.snippets_tab.temp_snippet_keybinding)
.changed()
{
*changes_this_frame = true;
}
if !settings.snippets_tab.temp_snippet_keybinding.is_empty() {
let exclude_id = if let Some(i) = edit_index {
settings.config.snippets.get(i).map(|s| s.id.as_ref())
} else {
None
};
if let Some(conflict) = settings.check_keybinding_conflict(
&settings.snippets_tab.temp_snippet_keybinding,
exclude_id,
) {
ui.label(
egui::RichText::new(format!("⚠️ {}", conflict))
.color(egui::Color32::from_rgb(255, 180, 0))
.small(),
);
}
}
if ui
.small_button("🎤")
.on_hover_text("Record keybinding")
.clicked()
{
settings.snippets_tab.recording_snippet_keybinding = true;
settings.snippets_tab.snippet_recorded_combo = None;
}
}
});
if !settings.snippets_tab.temp_snippet_keybinding.is_empty() {
ui.horizontal(|ui| {
if ui
.checkbox(
&mut settings.snippets_tab.temp_snippet_keybinding_enabled,
"Enabled",
)
.changed()
{
*changes_this_frame = true;
}
ui.label(
egui::RichText::new("(disable without removing)")
.small()
.color(egui::Color32::GRAY),
);
});
}
ui.label("Folder:");
if ui
.text_edit_singleline(&mut settings.snippets_tab.temp_snippet_folder)
.changed()
{
*changes_this_frame = true;
}
ui.label("Description:");
if ui
.text_edit_singleline(&mut settings.snippets_tab.temp_snippet_description)
.changed()
{
*changes_this_frame = true;
}
let var_count = settings.snippets_tab.temp_snippet_variables.len();
let header_text = if var_count > 0 {
format!("Custom Variables ({})", var_count)
} else {
"Custom Variables".to_string()
};
ui.add_space(4.0);
collapsing_section(
ui,
&header_text,
"snippet_custom_variables",
true,
collapsed,
|ui| {
let mut delete_var_index: Option<usize> = None;
if !settings.snippets_tab.temp_snippet_variables.is_empty() {
egui::Grid::new("snippet_variables_edit_grid")
.num_columns(3)
.spacing([8.0, 4.0])
.show(ui, |ui| {
ui.label(egui::RichText::new("Name").small().strong());
ui.label(egui::RichText::new("Value").small().strong());
ui.label(""); ui.end_row();
for i in 0..settings.snippets_tab.temp_snippet_variables.len() {
let (ref mut name, ref mut value) =
settings.snippets_tab.temp_snippet_variables[i];
let name_response = ui.add(
egui::TextEdit::singleline(name)
.desired_width(120.0)
.hint_text("name"),
);
if name_response.changed() {
*changes_this_frame = true;
}
let value_response = ui.add(
egui::TextEdit::singleline(value)
.desired_width(160.0)
.hint_text("value"),
);
if value_response.changed() {
*changes_this_frame = true;
}
if ui
.small_button(
egui::RichText::new("✕")
.color(egui::Color32::from_rgb(200, 80, 80)),
)
.on_hover_text("Remove variable")
.clicked()
{
delete_var_index = Some(i);
}
ui.end_row();
if name.is_empty() {
ui.label(
egui::RichText::new("⚠ Name required")
.small()
.color(egui::Color32::from_rgb(255, 180, 0)),
);
ui.label("");
ui.label("");
ui.end_row();
}
}
});
}
if let Some(idx) = delete_var_index {
settings.snippets_tab.temp_snippet_variables.remove(idx);
*changes_this_frame = true;
}
if ui.small_button("+ Add Variable").clicked() {
settings
.snippets_tab
.temp_snippet_variables
.push((String::new(), String::new()));
*changes_this_frame = true;
}
ui.label(
egui::RichText::new("Use \\(name) in content to reference custom variables")
.small()
.color(egui::Color32::GRAY),
);
},
);
ui.label(
egui::RichText::new("💡 Variables: \\(date), \\(time), \\(session.path), etc.")
.small()
.color(egui::Color32::GRAY),
);
});
ui.separator();
}
pub(super) fn build_snippet_from_form(
settings: &SettingsUI,
edit_index: Option<usize>,
) -> SnippetConfig {
SnippetConfig {
id: settings.snippets_tab.temp_snippet_id.clone(),
title: settings.snippets_tab.temp_snippet_title.clone(),
content: settings.snippets_tab.temp_snippet_content.clone(),
keybinding: if settings.snippets_tab.temp_snippet_keybinding.is_empty() {
None
} else {
Some(settings.snippets_tab.temp_snippet_keybinding.clone())
},
keybinding_enabled: settings.snippets_tab.temp_snippet_keybinding_enabled,
folder: if settings.snippets_tab.temp_snippet_folder.is_empty() {
None
} else {
Some(settings.snippets_tab.temp_snippet_folder.clone())
},
enabled: edit_index
.and_then(|i| settings.config.snippets.get(i))
.is_none_or(|existing| existing.enabled),
description: if settings.snippets_tab.temp_snippet_description.is_empty() {
None
} else {
Some(settings.snippets_tab.temp_snippet_description.clone())
},
auto_execute: settings.snippets_tab.temp_snippet_auto_execute,
variables: settings
.snippets_tab
.temp_snippet_variables
.iter()
.cloned()
.collect(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use par_term_config::Config;
#[test]
fn editing_a_disabled_snippet_keeps_it_disabled() {
let mut snippet = SnippetConfig::new(
"s1".to_string(),
"Greeting".to_string(),
"hello".to_string(),
);
snippet.enabled = false;
let config = Config {
snippets: vec![snippet],
..Config::default()
};
let mut settings = SettingsUI::new_for_tests(config);
settings.snippets_tab.temp_snippet_id = "s1".to_string();
settings.snippets_tab.temp_snippet_title = "Greeting (edited)".to_string();
let saved = build_snippet_from_form(&settings, Some(0));
assert!(!saved.enabled);
assert_eq!(saved.title, "Greeting (edited)");
}
#[test]
fn a_new_snippet_starts_enabled() {
let settings = SettingsUI::new_for_tests(Config::default());
assert!(build_snippet_from_form(&settings, None).enabled);
}
}