use super::super::*;
use crate::commands::settings::Shape;
use crate::lookout::field::shep_toml_shape;
impl App {
pub(super) fn cycle_setting(&mut self) -> Effect {
if self
.settings_mut()
.is_some_and(|s| s.pending.is_none() && s.toggle_fold())
{
return Effect::None;
}
if let Some(SettingsRow::DogKey { key, .. }) = self.settings().and_then(Settings::cursor)
&& let Some(reason) = key.read_only_reason()
{
self.notice = Some(Notice::grave(reason.to_string()));
return Effect::None;
}
if self.cursor_opens_something() {
return Effect::None;
}
if self.authorize_write().is_none() {
return Effect::None;
}
let Some(cursor) = self.settings().and_then(Settings::cursor) else {
return Effect::None;
};
match cursor {
SettingsRow::Field(index) => self.cycle_key(index),
SettingsRow::Dog(index) => self.cycle_dog(index),
SettingsRow::DogKey { dog, key } => self.cycle_dog_key(dog, key),
SettingsRow::DogGrant { dog, index } => self.cycle_dog_grant(dog, index),
SettingsRow::DogField { .. }
| SettingsRow::DogEnv(_)
| SettingsRow::DogEnvKey { .. }
| SettingsRow::DogEnvNew(_)
| SettingsRow::Table(_)
| SettingsRow::Entry { .. }
| SettingsRow::AddEntry(_)
| SettingsRow::AvailableDog(_)
| SettingsRow::BrowseDogs => Effect::None,
}
}
fn cursor_opens_something(&self) -> bool {
matches!(
self.settings().and_then(Settings::cursor),
Some(SettingsRow::AvailableDog(_) | SettingsRow::BrowseDogs)
)
}
fn cycle_key(&mut self, index: usize) -> Effect {
let shape = shep_toml_shape();
let field = &shape.fields.fields()[index];
let Some(settings) = self.body.settings_mut() else {
return Effect::None;
};
let (Some(value), Some(path), Some(kind)) = (
settings.next_candidate(field),
shape.paths.get(&field.key),
Shape::of(&field.kind),
) else {
return Effect::None;
};
let edit = SettingEdit::Set {
path: path.clone(),
shape: kind,
value,
};
let text = confirm_text(&edit, settings.snapshot.style.1);
settings.pending = Some(Pending::Armed {
edit,
text,
at: self.now,
});
Effect::None
}
pub(super) fn remove_setting(&mut self) -> Effect {
if self.disarm_settings_candidate() {
return Effect::None;
}
if self.arm_env_removal() {
return Effect::None;
}
if self.cursor_opens_something() {
return Effect::None;
}
if self.authorize_write().is_none() {
return Effect::None;
}
let now = self.now;
let Some(settings) = self.body.settings_mut() else {
return Effect::None;
};
if let Some(edit) = settings.removal_at_cursor() {
let text = confirm_text(&edit, settings.snapshot.style.1);
settings.pending = Some(Pending::Armed {
edit,
text,
at: now,
});
}
Effect::None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lookout::view::fixtures;
#[test]
fn space_advances_the_candidate_rather_than_needing_a_cancel() {
let mut app = fixtures::app_in_settings_on("daemon.log_level");
let _ = app.update(Msg::Key(KeyPress::Cycle));
let first = app.settings().unwrap().pending().unwrap().text.to_string();
let _ = app.update(Msg::Key(KeyPress::Cycle));
let second = app.settings().unwrap().pending().unwrap().text.to_string();
assert_ne!(first, second);
}
#[test]
fn the_style_cycle_from_a_silent_file_does_not_propose_the_level_in_force() {
let mut app = fixtures::app_in_settings_with_silent_style();
let _ = app.update(Msg::Key(KeyPress::Cycle));
let text = app.settings().unwrap().pending().unwrap().text.to_string();
assert!(
text.contains("set style level to plain"),
"the file says nothing, so full is in force and one step is plain: {text}"
);
}
#[test]
fn the_style_cycle_starts_from_the_file_and_not_the_level_in_force() {
let mut app = fixtures::app_in_settings_with_shadowed_style(StyleSource::Env);
let _ = app.update(Msg::Key(KeyPress::Cycle));
let text = app.settings().unwrap().pending().unwrap().text.to_string();
assert!(
text.contains("set style level to plain"),
"the file says full, so one step is plain: {text}"
);
}
#[test]
fn space_and_a_typed_character_on_an_available_dog_change_nothing() {
let mut app = fixtures::app_in_settings_with_an_index();
fixtures::select_setting(&mut app, "available.otel");
assert_eq!(app.update(Msg::Key(KeyPress::Cycle)), Effect::None);
assert!(
app.notice().is_none(),
"no read-only refusal for a row that writes nothing"
);
assert_eq!(app.update(Msg::Key(KeyPress::TextChar('x'))), Effect::None);
let settings = app.settings().expect("the screen stays open");
assert_eq!(settings.cursor(), Some(SettingsRow::AvailableDog(0)));
assert!(settings.pending().is_none(), "nothing armed");
assert!(settings.typing().is_none(), "no editor opened");
assert!(app.notice().is_none(), "not even a refusal");
assert!(matches!(app.body(), Body::Settings(_)));
}
#[test]
fn space_and_a_typed_character_on_browse_dogs_change_nothing() {
let mut app = fixtures::app_in_settings_on("dogs.browse");
assert_eq!(app.update(Msg::Key(KeyPress::Cycle)), Effect::None);
assert_eq!(app.update(Msg::Key(KeyPress::TextChar('x'))), Effect::None);
let settings = app.settings().expect("the screen stays open");
assert_eq!(settings.cursor(), Some(SettingsRow::BrowseDogs));
assert!(settings.pending().is_none(), "nothing armed");
assert!(settings.typing().is_none(), "no editor opened");
assert!(app.notice().is_none(), "not even a refusal");
assert!(matches!(app.body(), Body::Settings(_)));
}
#[test]
fn remove_on_an_available_dog_or_browse_dogs_gives_no_refusal() {
let mut app = fixtures::app_in_settings_with_an_index();
for name in ["available.otel", "dogs.browse"] {
fixtures::select_setting(&mut app, name);
assert_eq!(app.update(Msg::Key(KeyPress::Remove)), Effect::None);
assert!(app.notice().is_none(), "no refusal on {name}");
assert!(app.settings().unwrap().pending().is_none());
}
}
}