mail/compose.rs: add ability to kill embed process

If embed editor process is unresponsive, there was no way to kill it.
Add force kill option by pressing Ctrl+C.
tables
Manos Pitsidianakis 2022-08-27 14:45:27 +03:00
parent 9cb66ef818
commit f4e0970d46
2 changed files with 108 additions and 35 deletions

View File

@ -57,6 +57,13 @@ enum EmbedStatus {
Running(Arc<Mutex<EmbedTerminal>>, File),
}
impl EmbedStatus {
#[inline(always)]
fn is_stopped(&self) -> bool {
matches!(self, Self::Stopped(_, _))
}
}
impl std::ops::Deref for EmbedStatus {
type Target = Arc<Mutex<EmbedTerminal>>;
fn deref(&self) -> &Arc<Mutex<EmbedTerminal>> {
@ -696,6 +703,33 @@ To: {}
}
}
}
fn update_from_file(&mut self, file: File, context: &mut Context) -> bool {
let result = file.read_to_string();
match Draft::from_str(result.as_str()) {
Ok(mut new_draft) => {
std::mem::swap(self.draft.attachments_mut(), new_draft.attachments_mut());
if self.draft != new_draft {
self.has_changes = true;
}
self.draft = new_draft;
true
}
Err(err) => {
context.replies.push_back(UIEvent::Notification(
Some("Could not parse draft headers correctly.".to_string()),
format!(
"{}\nThe invalid text has been set as the body of your draft",
&err
),
Some(NotificationType::Error(melib::error::ErrorKind::None)),
));
self.draft.set_body(result);
self.has_changes = true;
false
}
}
}
}
impl Component for Composer {
@ -854,7 +888,23 @@ impl Component for Composer {
((0, 0), pos_dec(guard.grid.terminal_size, (1, 1))),
);
change_colors(grid, embed_area, Color::Byte(8), theme_default.bg);
const STOPPED_MESSAGE: &str = "process has stopped, press 'e' to re-activate";
let our_map: ShortcutMap =
account_settings!(context[self.account_hash].shortcuts.composing)
.key_values();
let mut shortcuts: ShortcutMaps = Default::default();
shortcuts.insert(Composer::DESCRIPTION, our_map);
let stopped_message: String =
format!("Process with PID {} has stopped.", guard.child_pid);
let stopped_message_2: String = format!(
"-press '{}' (edit_mail shortcut) to re-activate.",
shortcuts[Self::DESCRIPTION]["edit_mail"]
);
const STOPPED_MESSAGE_3: &str =
"-press Ctrl-C to forcefully kill it and return to editor.";
let max_len = std::cmp::max(
stopped_message.len(),
std::cmp::max(stopped_message_2.len(), STOPPED_MESSAGE_3.len()),
);
let inner_area = create_box(
grid,
(
@ -862,22 +912,34 @@ impl Component for Composer {
pos_inc(
upper_left!(body_area),
(
std::cmp::min(STOPPED_MESSAGE.len() + 5, width!(body_area)),
std::cmp::min(max_len + 5, width!(body_area)),
std::cmp::min(5, height!(body_area)),
),
),
),
);
clear_area(grid, inner_area, theme_default);
write_string_to_grid(
STOPPED_MESSAGE,
grid,
theme_default.fg,
theme_default.bg,
theme_default.attrs,
inner_area,
Some(get_x(upper_left!(inner_area))),
);
for (i, l) in [
stopped_message.as_str(),
stopped_message_2.as_str(),
STOPPED_MESSAGE_3,
]
.iter()
.enumerate()
{
write_string_to_grid(
l,
grid,
theme_default.fg,
theme_default.bg,
theme_default.attrs,
(
pos_inc((0, i), upper_left!(inner_area)),
bottom_right!(inner_area),
),
Some(get_x(upper_left!(inner_area))),
);
}
context.dirty_areas.push_back(area);
self.dirty = false;
return;
@ -1356,6 +1418,7 @@ impl Component for Composer {
match embed_guard.is_active() {
Ok(WaitStatus::Exited(_, exit_code)) => {
drop(embed_guard);
let embed = self.embed.take();
if exit_code != 0 {
context.replies.push_back(UIEvent::Notification(
None,
@ -1367,31 +1430,9 @@ impl Component for Composer {
melib::error::ErrorKind::External,
)),
));
} else if let EmbedStatus::Running(_, f) = embed {
let result = f.read_to_string();
match Draft::from_str(result.as_str()) {
Ok(mut new_draft) => {
std::mem::swap(
self.draft.attachments_mut(),
new_draft.attachments_mut(),
);
if self.draft != new_draft {
self.has_changes = true;
}
self.draft = new_draft;
}
Err(err) => {
context.replies.push_back(UIEvent::Notification(
Some("Could not parse draft headers correctly.".to_string()),
format!("{}\nThe invalid text has been set as the body of your draft", &err),
Some(NotificationType::Error(melib::error::ErrorKind::None)),
));
self.draft.set_body(result);
self.has_changes = true;
}
}
} else if let Some(EmbedStatus::Running(_, file)) = embed {
self.update_from_file(file, context);
}
self.embed = None;
self.initialized = false;
self.mode = ViewMode::Edit;
self.set_dirty(true);
@ -1587,6 +1628,32 @@ impl Component for Composer {
self.set_dirty(true);
return true;
}
UIEvent::Input(Key::Ctrl('c'))
if self.embed.is_some() && self.embed.as_ref().unwrap().is_stopped() =>
{
match self.embed.take() {
Some(EmbedStatus::Running(embed, file))
| Some(EmbedStatus::Stopped(embed, file)) => {
let guard = embed.lock().unwrap();
guard.wake_up();
guard.terminate();
self.update_from_file(file, context);
}
_ => {}
}
context.replies.push_back(UIEvent::Notification(
None,
"Subprocess was killed by SIGTERM signal".to_string(),
Some(NotificationType::Error(melib::error::ErrorKind::External)),
));
self.initialized = false;
self.mode = ViewMode::Edit;
context
.replies
.push_back(UIEvent::ChangeMode(UIMode::Normal));
self.set_dirty(true);
return true;
}
UIEvent::Input(ref key)
if self.mode.is_edit()
&& shortcut!(key == shortcuts[Self::DESCRIPTION]["edit_mail"]) =>

View File

@ -109,6 +109,12 @@ impl EmbedTerminal {
let _ = nix::sys::signal::kill(debug!(self.child_pid), nix::sys::signal::SIGSTOP);
}
pub fn terminate(&self) {
let _ = nix::sys::signal::kill(self.child_pid, nix::sys::signal::SIGTERM);
std::thread::sleep(std::time::Duration::from_millis(150));
let _ = waitpid(self.child_pid, Some(WaitPidFlag::WNOHANG));
}
pub fn is_active(&self) -> Result<WaitStatus> {
debug!(waitpid(self.child_pid, Some(WaitPidFlag::WNOHANG),))
.map_err(|e| MeliError::new(e.to_string()))