use super::super::*;
impl App {
pub(in crate::lookout::app) fn on_settings_text_key(&mut self, key: KeyPress) -> Effect {
if self
.settings()
.is_some_and(|settings| settings.env_typing().is_some())
{
return self.on_settings_env_key(key);
}
let now = self.now;
let Some(settings) = self.settings_mut() else {
return Effect::None;
};
match key {
KeyPress::Quit => return Effect::Quit,
KeyPress::TextChar(typed) => {
if let Some(Pending::Typing { buffer, .. }) = settings.pending.as_mut() {
buffer.insert(typed);
}
}
KeyPress::TextBackspace => {
if let Some(Pending::Typing { buffer, .. }) = settings.pending.as_mut() {
buffer.backspace();
}
}
KeyPress::TextEdit(edit) => {
if let Some(Pending::Typing { buffer, .. }) = settings.pending.as_mut() {
buffer.edit(edit);
}
}
KeyPress::TextApply => {
if let Some(Pending::Typing { target, buffer }) = settings.pending.take() {
match target.edit(settings, buffer.as_str()) {
Ok(TargetEdit::Setting(edit)) => {
let text = confirm_text(&edit, settings.snapshot.style.1);
settings.pending = Some(Pending::Armed {
edit,
text,
at: now,
});
}
Ok(TargetEdit::DogSection { name, toml }) => {
let text = format!(
"write {name}'s section? it lands in dogs.toml, {name} is told, and \
its comments and key order stay"
);
settings.pending = Some(Pending::DogSectionArmed {
name,
toml,
text,
at: now,
});
}
Err(why) => {
settings.pending = Some(Pending::Typing { target, buffer });
self.notice = Some(Notice::grave(why));
return Effect::None;
}
}
}
self.mode = InputMode::Normal;
}
KeyPress::TextAbandon => {
settings.pending = None;
self.mode = InputMode::Normal;
}
_ => {}
}
Effect::None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lookout::view::fixtures;
#[test]
fn enter_on_a_text_row_opens_the_editor_seeded_with_the_current_value() {
let mut app = fixtures::app_in_settings_on("daemon.max_cron_sleep");
let _ = app.update(Msg::Key(KeyPress::Confirm));
let (label, buffer) = app.settings().unwrap().typing().expect("the editor opens");
assert_eq!(label, "daemon.max_cron_sleep");
assert_eq!(buffer.as_str(), "30s");
}
#[test]
fn typing_then_enter_arms_rather_than_writing() {
let mut app = fixtures::app_in_settings_on("daemon.max_cron_sleep");
let _ = app.update(Msg::Key(KeyPress::Confirm));
for _ in 0..3 {
let _ = app.update(Msg::Key(KeyPress::TextBackspace));
}
for c in "45s".chars() {
let _ = app.update(Msg::Key(KeyPress::TextChar(c)));
}
assert_eq!(app.update(Msg::Key(KeyPress::TextApply)), Effect::None);
let prompt = app.settings().unwrap().pending().unwrap();
assert!(
!prompt.sent,
"the editor arms; a second Enter is what sends"
);
assert!(prompt.text.contains("45s"), "got: {}", prompt.text);
assert!(
prompt.text.contains("SHEP_MAX_CRON_SLEEP"),
"got: {}",
prompt.text
);
}
#[test]
fn an_empty_editor_arms_an_unset() {
let mut app = fixtures::app_in_settings_on("daemon.max_cron_sleep");
let _ = app.update(Msg::Key(KeyPress::Confirm));
for _ in 0..8 {
let _ = app.update(Msg::Key(KeyPress::TextBackspace));
}
let _ = app.update(Msg::Key(KeyPress::TextApply));
let text = app.settings().unwrap().pending().unwrap().text.to_string();
assert!(
text.starts_with("unset daemon.max_cron_sleep?"),
"got: {text}"
);
}
#[test]
fn the_socket_confirm_rules_out_the_reload_it_would_otherwise_imply() {
let mut app = fixtures::app_in_settings_on("daemon.socket");
let _ = app.update(Msg::Key(KeyPress::Confirm));
let _ = app.update(Msg::Key(KeyPress::TextApply));
let text = app.settings().unwrap().pending().unwrap().text.to_string();
assert!(text.contains("stopped and started"), "got: {text}");
assert!(text.contains("a reload will not move it"), "got: {text}");
}
#[test]
fn a_refused_write_reopens_the_editor_with_the_text_intact() {
let mut app = fixtures::app_in_settings_on("daemon.max_cron_sleep");
let _ = app.update(Msg::Key(KeyPress::Confirm));
for _ in 0..3 {
let _ = app.update(Msg::Key(KeyPress::TextBackspace));
}
for c in "500ms".chars() {
let _ = app.update(Msg::Key(KeyPress::TextChar(c)));
}
let _ = app.update(Msg::Key(KeyPress::TextApply));
let Effect::WriteSetting { edit, ticket, .. } = app.update(Msg::Key(KeyPress::Confirm))
else {
panic!("Enter must send");
};
let _ = app.update(Msg::SettingWritten {
edit,
ticket,
result: Err("max_cron_sleep is 500ms, below the 1s floor".into()),
});
let (_, buffer) = app
.settings()
.unwrap()
.typing()
.expect("the editor reopens");
assert_eq!(buffer.as_str(), "500ms");
assert_eq!(
app.mode(),
InputMode::Text,
"a reopened editor owns the keyboard, or the text is unreachable"
);
assert!(
app.notice()
.unwrap()
.to_string()
.contains("below the 1s floor")
);
}
#[test]
fn escape_abandons_the_editor_and_keeps_the_screen_open() {
let mut app = fixtures::app_in_settings_on("daemon.socket");
let _ = app.update(Msg::Key(KeyPress::Confirm));
let _ = app.update(Msg::Key(KeyPress::TextAbandon));
assert!(app.settings().unwrap().typing().is_none());
assert!(app.settings().is_some());
}
#[test]
fn a_closed_scalar_has_no_editor() {
let mut app = fixtures::app_in_settings_on("daemon.log_level");
let _ = app.update(Msg::Key(KeyPress::Confirm));
assert!(
app.settings().unwrap().typing().is_none(),
"log_level is a cycle, not a text field"
);
}
#[test]
fn a_list_opens_seeded_with_its_items_comma_separated() {
let mut app = fixtures::app_in_settings_on("hosts");
let _ = app.update(Msg::Key(KeyPress::Confirm));
let (label, buffer) = app.settings().unwrap().typing().expect("the editor opens");
assert_eq!(label, "hosts");
assert_eq!(buffer.as_str(), "ada@box");
}
#[test]
fn enter_on_a_map_entry_edits_that_entry() {
let mut app = fixtures::app_in_settings_on("interpreters.py");
let _ = app.update(Msg::Key(KeyPress::Confirm));
let (label, buffer) = app.settings().unwrap().typing().expect("the editor opens");
assert_eq!(label, "interpreters.py");
assert_eq!(buffer.as_str(), "python3");
}
#[test]
fn a_new_entry_needs_a_name_and_a_value() {
let mut app = fixtures::app_in_settings_on("interpreters+");
let _ = app.update(Msg::Key(KeyPress::Confirm));
for c in "rb".chars() {
let _ = app.update(Msg::Key(KeyPress::TextChar(c)));
}
let _ = app.update(Msg::Key(KeyPress::TextApply));
assert!(
app.settings().unwrap().typing().is_some(),
"a bare name keeps the editor open"
);
assert!(app.settings().unwrap().pending().is_none());
for c in "=ruby".chars() {
let _ = app.update(Msg::Key(KeyPress::TextChar(c)));
}
let _ = app.update(Msg::Key(KeyPress::TextApply));
let text = app.settings().unwrap().pending().unwrap().text.to_string();
assert!(
text.starts_with("set interpreters.rb to ruby?"),
"got: {text}"
);
}
}