use crate::app::SnippetFormBaseline;
use crate::app::forms::{SnippetForm, SnippetOutputState, SnippetParamFormState};
use crate::snippet::{Snippet, SnippetStore};
pub struct SnippetState {
pub(in crate::app) store: SnippetStore,
pub(in crate::app) form: SnippetForm,
pub(in crate::app) pending: Option<(Snippet, Vec<String>)>,
pub(in crate::app) output: Option<SnippetOutputState>,
pub(in crate::app) param_form: Option<SnippetParamFormState>,
pub(in crate::app) pending_terminal: bool,
pub(in crate::app) form_baseline: Option<SnippetFormBaseline>,
pub(in crate::app) pending_delete: Option<usize>,
pub(in crate::app) flow_targets: Vec<String>,
pub(in crate::app) form_editing: Option<usize>,
pub(in crate::app) param_snippet: Option<Snippet>,
pub(in crate::app) output_snippet_name: Option<String>,
}
impl Default for SnippetState {
fn default() -> Self {
Self {
store: SnippetStore::default(),
form: SnippetForm::new(),
pending: None,
output: None,
param_form: None,
pending_terminal: false,
form_baseline: None,
pending_delete: None,
flow_targets: Vec::new(),
form_editing: None,
param_snippet: None,
output_snippet_name: None,
}
}
}
impl SnippetState {
pub fn store(&self) -> &SnippetStore {
&self.store
}
pub fn store_mut(&mut self) -> &mut SnippetStore {
&mut self.store
}
pub fn form(&self) -> &SnippetForm {
&self.form
}
pub fn form_mut(&mut self) -> &mut SnippetForm {
&mut self.form
}
pub fn output(&self) -> Option<&SnippetOutputState> {
self.output.as_ref()
}
pub fn output_mut(&mut self) -> Option<&mut SnippetOutputState> {
self.output.as_mut()
}
pub fn set_output(&mut self, output: Option<SnippetOutputState>) {
self.output = output;
}
pub fn take_output(&mut self) -> Option<SnippetOutputState> {
self.output.take()
}
pub fn param_form(&self) -> Option<&SnippetParamFormState> {
self.param_form.as_ref()
}
pub fn param_form_mut(&mut self) -> Option<&mut SnippetParamFormState> {
self.param_form.as_mut()
}
pub fn set_param_form(&mut self, param_form: Option<SnippetParamFormState>) {
self.param_form = param_form;
}
pub fn pending_delete(&self) -> Option<usize> {
self.pending_delete
}
pub fn take_pending_delete(&mut self) -> Option<usize> {
self.pending_delete.take()
}
pub fn pending(&self) -> Option<&(Snippet, Vec<String>)> {
self.pending.as_ref()
}
pub fn take_pending(&mut self) -> Option<(Snippet, Vec<String>)> {
self.pending.take()
}
pub fn set_pending(&mut self, value: Option<(Snippet, Vec<String>)>) {
self.pending = value;
}
pub fn flow_targets(&self) -> &[String] {
&self.flow_targets
}
pub fn set_flow_targets(&mut self, targets: Vec<String>) {
self.flow_targets = targets;
}
pub fn clear_flow_targets(&mut self) {
self.flow_targets.clear();
}
pub fn form_editing(&self) -> Option<usize> {
self.form_editing
}
pub fn set_form_editing(&mut self, editing: Option<usize>) {
self.form_editing = editing;
}
pub fn param_snippet(&self) -> Option<&Snippet> {
self.param_snippet.as_ref()
}
pub fn set_param_snippet(&mut self, snippet: Option<Snippet>) {
self.param_snippet = snippet;
}
pub fn output_snippet_name(&self) -> Option<&str> {
self.output_snippet_name.as_deref()
}
pub fn set_output_snippet_name(&mut self, name: Option<String>) {
self.output_snippet_name = name;
}
pub fn pending_terminal(&self) -> bool {
self.pending_terminal
}
pub fn set_pending_terminal(&mut self, value: bool) {
self.pending_terminal = value;
}
pub fn form_baseline(&self) -> Option<&SnippetFormBaseline> {
self.form_baseline.as_ref()
}
pub fn form_is_dirty(&self) -> bool {
match &self.form_baseline {
Some(b) => {
self.form.name != b.name
|| self.form.command != b.command
|| self.form.description != b.description
}
None => false,
}
}
pub fn set_form_baseline(&mut self, baseline: Option<SnippetFormBaseline>) {
self.form_baseline = baseline;
}
pub fn with_store_loaded(paths: Option<&crate::runtime::env::Paths>) -> Self {
Self {
store: crate::snippet::SnippetStore::load(paths),
..Self::default()
}
}
pub fn request_delete(&mut self, idx: usize) {
self.pending_delete = Some(idx);
}
pub fn cancel_delete(&mut self) {
self.pending_delete = None;
}
pub fn close_param_form(&mut self) {
self.param_form = None;
self.pending_terminal = false;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_empty() {
let s = SnippetState::default();
assert!(s.pending.is_none());
assert!(s.output.is_none());
assert!(s.param_form.is_none());
assert!(!s.pending_terminal);
assert!(s.form_baseline.is_none());
assert!(s.pending_delete.is_none());
}
#[test]
fn request_delete_sets_pending_delete_to_some_idx() {
let mut s = SnippetState::default();
s.request_delete(3);
assert_eq!(s.pending_delete, Some(3));
}
#[test]
fn cancel_delete_clears_pending_delete() {
let mut s = SnippetState {
pending_delete: Some(2),
..Default::default()
};
s.cancel_delete();
assert!(s.pending_delete.is_none());
}
#[test]
fn request_delete_overwrites_existing_pending() {
let mut s = SnippetState {
pending_delete: Some(1),
..Default::default()
};
s.request_delete(7);
assert_eq!(s.pending_delete, Some(7));
}
#[test]
fn close_param_form_clears_param_form_and_pending_terminal() {
let mut s = SnippetState {
param_form: Some(SnippetParamFormState::new(&[])),
pending_terminal: true,
..Default::default()
};
s.close_param_form();
assert!(s.param_form.is_none());
assert!(!s.pending_terminal);
}
#[test]
fn close_param_form_preserves_pending_output_and_store() {
use crate::snippet::Snippet;
let mut s = SnippetState {
param_form: Some(SnippetParamFormState::new(&[])),
pending_terminal: true,
pending: Some((
Snippet {
name: "ls".into(),
command: "ls -la".into(),
description: String::new(),
},
vec!["host-a".into()],
)),
..Default::default()
};
s.close_param_form();
assert!(
s.pending.is_some(),
"pending stays for the consumer to read"
);
assert!(s.pending_delete.is_none());
}
#[test]
fn close_param_form_is_idempotent_when_already_none() {
let mut s = SnippetState::default();
s.close_param_form();
s.close_param_form();
assert!(s.param_form.is_none());
assert!(!s.pending_terminal);
}
fn state_matching_baseline() -> SnippetState {
let mut s = SnippetState::default();
s.form.name = "deploy".into();
s.form.command = "make deploy".into();
s.form.description = "ship it".into();
s.set_form_baseline(Some(SnippetFormBaseline {
name: "deploy".into(),
command: "make deploy".into(),
description: "ship it".into(),
}));
s
}
#[test]
fn form_is_dirty_is_false_without_a_baseline() {
let mut s = SnippetState::default();
s.form.name = "edited".into();
assert!(!s.form_is_dirty());
}
#[test]
fn form_is_dirty_is_false_when_form_equals_baseline() {
assert!(!state_matching_baseline().form_is_dirty());
}
fn assert_field_change_is_dirty(field: &str, mutate: impl FnOnce(&mut SnippetForm)) {
let mut s = state_matching_baseline();
mutate(&mut s.form);
assert!(s.form_is_dirty(), "a change in {field} must read dirty");
}
#[test]
fn form_is_dirty_detects_a_change_in_each_field() {
assert_field_change_is_dirty("name", |f| f.name.push('x'));
assert_field_change_is_dirty("command", |f| f.command.push('x'));
assert_field_change_is_dirty("description", |f| f.description.push('x'));
}
}