Add error popup when external editor fails

This commit is contained in:
Joscha 2022-09-25 18:54:56 +02:00
parent 4c7ac31699
commit 9aac9f6fdd
10 changed files with 95 additions and 90 deletions

View file

@ -17,6 +17,7 @@ Procedure when bumping the version number:
### Added ### Added
- Room deletion confirmation popup - Room deletion confirmation popup
- Message inspection popup - Message inspection popup
- Error popup when external editor fails
### Fixed ### Fixed
- Cursor being visible through popups - Cursor being visible through popups

View file

@ -5,6 +5,7 @@
mod blocks; mod blocks;
mod tree; mod tree;
use std::io;
use std::sync::Arc; use std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
@ -79,6 +80,7 @@ pub enum Reaction<M: Msg> {
parent: Option<M::Id>, parent: Option<M::Id>,
content: String, content: String,
}, },
ComposeError(io::Error),
} }
impl<M: Msg> Reaction<M> { impl<M: Msg> Reaction<M> {

View file

@ -225,7 +225,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
fn list_editor_key_bindings(&self, bindings: &mut KeyBindingsList) { fn list_editor_key_bindings(&self, bindings: &mut KeyBindingsList) {
bindings.binding("esc", "close editor"); bindings.binding("esc", "close editor");
bindings.binding("enter", "send message"); bindings.binding("enter", "send message");
util::list_editor_key_bindings(bindings, |_| true, true); util::list_editor_key_bindings_allowing_external_editing(bindings, |_| true);
} }
fn handle_editor_input_event( fn handle_editor_input_event(
@ -256,16 +256,17 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
} }
_ => { _ => {
let handled = util::handle_editor_input_event( let handled = util::handle_editor_input_event_allowing_external_editing(
&self.editor, &self.editor,
terminal, terminal,
crossterm_lock, crossterm_lock,
event, event,
|_| true, |_| true,
true,
); );
if !handled { match handled {
return Reaction::NotHandled; Ok(true) => {}
Ok(false) => return Reaction::NotHandled,
Err(e) => return Reaction::ComposeError(e),
} }
} }
} }

View file

@ -1,9 +1,6 @@
use std::sync::Arc;
use crossterm::style::{ContentStyle, Stylize}; use crossterm::style::{ContentStyle, Stylize};
use euphoxide::api::PersonalAccountView; use euphoxide::api::PersonalAccountView;
use euphoxide::conn::Status; use euphoxide::conn::Status;
use parking_lot::FairMutex;
use toss::terminal::Terminal; use toss::terminal::Terminal;
use crate::euph::Room; use crate::euph::Room;
@ -133,7 +130,7 @@ impl AccountUiState {
Focus::Password => bindings.binding("enter", "log in"), Focus::Password => bindings.binding("enter", "log in"),
} }
bindings.binding("tab", "switch focus"); bindings.binding("tab", "switch focus");
util::list_editor_key_bindings(bindings, |c| c != '\n', false); util::list_editor_key_bindings(bindings, |c| c != '\n');
} }
Self::LoggedIn(_) => bindings.binding("L", "log out"), Self::LoggedIn(_) => bindings.binding("L", "log out"),
} }
@ -142,7 +139,6 @@ impl AccountUiState {
pub fn handle_input_event( pub fn handle_input_event(
&mut self, &mut self,
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>,
event: &InputEvent, event: &InputEvent,
room: &Option<Room>, room: &Option<Room>,
) -> EventResult { ) -> EventResult {
@ -170,10 +166,8 @@ impl AccountUiState {
if util::handle_editor_input_event( if util::handle_editor_input_event(
&logged_out.email, &logged_out.email,
terminal, terminal,
crossterm_lock,
event, event,
|c| c != '\n', |c| c != '\n',
false,
) { ) {
EventResult::Handled EventResult::Handled
} else { } else {
@ -192,10 +186,8 @@ impl AccountUiState {
if util::handle_editor_input_event( if util::handle_editor_input_event(
&logged_out.password, &logged_out.password,
terminal, terminal,
crossterm_lock,
event, event,
|c| c != '\n', |c| c != '\n',
false,
) { ) {
EventResult::Handled EventResult::Handled
} else { } else {

View file

@ -1,6 +1,3 @@
use std::sync::Arc;
use parking_lot::FairMutex;
use toss::terminal::Terminal; use toss::terminal::Terminal;
use crate::euph::Room; use crate::euph::Room;
@ -23,7 +20,7 @@ pub fn widget(editor: &EditorState) -> BoxedWidget {
pub fn list_key_bindings(bindings: &mut KeyBindingsList) { pub fn list_key_bindings(bindings: &mut KeyBindingsList) {
bindings.binding("esc", "abort"); bindings.binding("esc", "abort");
bindings.binding("enter", "authenticate"); bindings.binding("enter", "authenticate");
util::list_editor_key_bindings(bindings, |_| true, false); util::list_editor_key_bindings(bindings, |_| true);
} }
pub enum EventResult { pub enum EventResult {
@ -34,7 +31,6 @@ pub enum EventResult {
pub fn handle_input_event( pub fn handle_input_event(
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>,
event: &InputEvent, event: &InputEvent,
room: &Option<Room>, room: &Option<Room>,
editor: &EditorState, editor: &EditorState,
@ -48,14 +44,7 @@ pub fn handle_input_event(
EventResult::ResetState EventResult::ResetState
} }
_ => { _ => {
if util::handle_editor_input_event( if util::handle_editor_input_event(editor, terminal, event, |_| true) {
editor,
terminal,
crossterm_lock,
event,
|_| true,
false,
) {
EventResult::Handled EventResult::Handled
} else { } else {
EventResult::NotHandled EventResult::NotHandled

View file

@ -1,7 +1,4 @@
use std::sync::Arc;
use euphoxide::conn::Joined; use euphoxide::conn::Joined;
use parking_lot::FairMutex;
use toss::styled::Styled; use toss::styled::Styled;
use toss::terminal::Terminal; use toss::terminal::Terminal;
@ -34,7 +31,7 @@ fn nick_char(c: char) -> bool {
pub fn list_key_bindings(bindings: &mut KeyBindingsList) { pub fn list_key_bindings(bindings: &mut KeyBindingsList) {
bindings.binding("esc", "abort"); bindings.binding("esc", "abort");
bindings.binding("enter", "set nick"); bindings.binding("enter", "set nick");
util::list_editor_key_bindings(bindings, nick_char, false); util::list_editor_key_bindings(bindings, nick_char);
} }
pub enum EventResult { pub enum EventResult {
@ -45,7 +42,6 @@ pub enum EventResult {
pub fn handle_input_event( pub fn handle_input_event(
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>,
event: &InputEvent, event: &InputEvent,
room: &Option<Room>, room: &Option<Room>,
editor: &EditorState, editor: &EditorState,
@ -59,14 +55,7 @@ pub fn handle_input_event(
EventResult::ResetState EventResult::ResetState
} }
_ => { _ => {
if util::handle_editor_input_event( if util::handle_editor_input_event(editor, terminal, event, nick_char) {
editor,
terminal,
crossterm_lock,
event,
nick_char,
false,
) {
EventResult::Handled EventResult::Handled
} else { } else {
EventResult::NotHandled EventResult::NotHandled

View file

@ -380,6 +380,13 @@ impl EuphRoom {
} }
return true; return true;
} }
Reaction::ComposeError(e) => {
self.popups.push_front(RoomPopup::Error {
description: "Failed to use external editor".to_string(),
reason: format!("{e}"),
});
return true;
}
} }
if self.handle_inspect_initiating_input_event(event).await { if self.handle_inspect_initiating_input_event(event).await {
@ -470,8 +477,7 @@ impl EuphRoom {
.await .await
} }
State::Auth(editor) => { State::Auth(editor) => {
match auth::handle_input_event(terminal, crossterm_lock, event, &self.room, editor) match auth::handle_input_event(terminal, event, &self.room, editor) {
{
auth::EventResult::NotHandled => false, auth::EventResult::NotHandled => false,
auth::EventResult::Handled => true, auth::EventResult::Handled => true,
auth::EventResult::ResetState => { auth::EventResult::ResetState => {
@ -481,8 +487,7 @@ impl EuphRoom {
} }
} }
State::Nick(editor) => { State::Nick(editor) => {
match nick::handle_input_event(terminal, crossterm_lock, event, &self.room, editor) match nick::handle_input_event(terminal, event, &self.room, editor) {
{
nick::EventResult::NotHandled => false, nick::EventResult::NotHandled => false,
nick::EventResult::Handled => true, nick::EventResult::Handled => true,
nick::EventResult::ResetState => { nick::EventResult::ResetState => {
@ -492,7 +497,7 @@ impl EuphRoom {
} }
} }
State::Account(account) => { State::Account(account) => {
match account.handle_input_event(terminal, crossterm_lock, event, &self.room) { match account.handle_input_event(terminal, event, &self.room) {
account::EventResult::NotHandled => false, account::EventResult::NotHandled => false,
account::EventResult::Handled => true, account::EventResult::Handled => true,
account::EventResult::ResetState => { account::EventResult::ResetState => {

View file

@ -352,13 +352,13 @@ impl Rooms {
bindings.heading("Rooms"); bindings.heading("Rooms");
bindings.binding("esc", "abort"); bindings.binding("esc", "abort");
bindings.binding("enter", "connect to room"); bindings.binding("enter", "connect to room");
util::list_editor_key_bindings(bindings, Self::room_char, false); util::list_editor_key_bindings(bindings, Self::room_char);
} }
State::Delete(_, _) => { State::Delete(_, _) => {
bindings.heading("Rooms"); bindings.heading("Rooms");
bindings.binding("esc", "abort"); bindings.binding("esc", "abort");
bindings.binding("enter", "delete room"); bindings.binding("enter", "delete room");
util::list_editor_key_bindings(bindings, Self::room_char, false); util::list_editor_key_bindings(bindings, Self::room_char);
} }
} }
} }
@ -470,16 +470,7 @@ impl Rooms {
self.state = State::ShowRoom(name); self.state = State::ShowRoom(name);
} }
} }
_ => { _ => return util::handle_editor_input_event(ed, terminal, event, Self::room_char),
return util::handle_editor_input_event(
ed,
terminal,
crossterm_lock,
event,
Self::room_char,
false,
)
}
}, },
State::Delete(name, editor) => match event { State::Delete(name, editor) => match event {
key!(Esc) => self.state = State::ShowList, key!(Esc) => self.state = State::ShowList,
@ -492,10 +483,8 @@ impl Rooms {
return util::handle_editor_input_event( return util::handle_editor_input_event(
editor, editor,
terminal, terminal,
crossterm_lock,
event, event,
Self::room_char, Self::room_char,
false,
) )
} }
}, },

View file

@ -1,3 +1,4 @@
use std::io;
use std::sync::Arc; use std::sync::Arc;
use parking_lot::FairMutex; use parking_lot::FairMutex;
@ -10,7 +11,7 @@ pub fn prompt(
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>, crossterm_lock: &Arc<FairMutex<()>>,
initial_text: &str, initial_text: &str,
) -> Option<String> { ) -> io::Result<String> {
let content = { let content = {
let _guard = crossterm_lock.lock(); let _guard = crossterm_lock.lock();
terminal.suspend().expect("could not suspend"); terminal.suspend().expect("could not suspend");
@ -19,38 +20,23 @@ pub fn prompt(
content content
}; };
// TODO Don't swipe this error under the rug content
let content = content.ok()?;
if content.trim().is_empty() {
None
} else {
Some(content)
}
} }
// TODO List key binding util functions fn list_editor_editing_key_bindings(
pub fn list_editor_key_bindings(
bindings: &mut KeyBindingsList, bindings: &mut KeyBindingsList,
char_filter: impl Fn(char) -> bool, char_filter: impl Fn(char) -> bool,
can_edit_externally: bool,
) { ) {
if char_filter('\n') { if char_filter('\n') {
bindings.binding("enter+<any modifier>", "insert newline"); bindings.binding("enter+<any modifier>", "insert newline");
} }
// Editing
bindings.binding("ctrl+h, backspace", "delete before cursor"); bindings.binding("ctrl+h, backspace", "delete before cursor");
bindings.binding("ctrl+d, delete", "delete after cursor"); bindings.binding("ctrl+d, delete", "delete after cursor");
bindings.binding("ctrl+l", "clear editor contents"); bindings.binding("ctrl+l", "clear editor contents");
if can_edit_externally { }
bindings.binding("ctrl+x", "edit in external editor");
}
bindings.empty(); fn list_editor_cursor_movement_key_bindings(bindings: &mut KeyBindingsList) {
// Cursor movement
bindings.binding("ctrl+b, ←", "move cursor left"); bindings.binding("ctrl+b, ←", "move cursor left");
bindings.binding("ctrl+f, →", "move cursor right"); bindings.binding("ctrl+f, →", "move cursor right");
bindings.binding("alt+b, ctrl+←", "move cursor left a word"); bindings.binding("alt+b, ctrl+←", "move cursor left a word");
@ -60,13 +46,20 @@ pub fn list_editor_key_bindings(
bindings.binding("↑/↓", "move cursor up/down"); bindings.binding("↑/↓", "move cursor up/down");
} }
pub fn list_editor_key_bindings(
bindings: &mut KeyBindingsList,
char_filter: impl Fn(char) -> bool,
) {
list_editor_editing_key_bindings(bindings, char_filter);
bindings.empty();
list_editor_cursor_movement_key_bindings(bindings);
}
pub fn handle_editor_input_event( pub fn handle_editor_input_event(
editor: &EditorState, editor: &EditorState,
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>,
event: &InputEvent, event: &InputEvent,
char_filter: impl Fn(char) -> bool, char_filter: impl Fn(char) -> bool,
can_edit_externally: bool,
) -> bool { ) -> bool {
match event { match event {
// Enter with *any* modifier pressed - if ctrl and shift don't // Enter with *any* modifier pressed - if ctrl and shift don't
@ -94,7 +87,6 @@ pub fn handle_editor_input_event(
key!(Ctrl + 'h') | key!(Backspace) => editor.backspace(terminal.frame()), key!(Ctrl + 'h') | key!(Backspace) => editor.backspace(terminal.frame()),
key!(Ctrl + 'd') | key!(Delete) => editor.delete(), key!(Ctrl + 'd') | key!(Delete) => editor.delete(),
key!(Ctrl + 'l') => editor.clear(), key!(Ctrl + 'l') => editor.clear(),
key!(Ctrl + 'x') if can_edit_externally => editor.edit_externally(terminal, crossterm_lock),
// TODO Key bindings to delete words // TODO Key bindings to delete words
// Cursor movement // Cursor movement
@ -112,3 +104,33 @@ pub fn handle_editor_input_event(
true true
} }
pub fn list_editor_key_bindings_allowing_external_editing(
bindings: &mut KeyBindingsList,
char_filter: impl Fn(char) -> bool,
) {
list_editor_editing_key_bindings(bindings, char_filter);
bindings.binding("ctrl+x", "edit in external editor");
bindings.empty();
list_editor_cursor_movement_key_bindings(bindings);
}
pub fn handle_editor_input_event_allowing_external_editing(
editor: &EditorState,
terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>,
event: &InputEvent,
char_filter: impl Fn(char) -> bool,
) -> io::Result<bool> {
if let key!(Ctrl + 'x') = event {
editor.edit_externally(terminal, crossterm_lock)?;
Ok(true)
} else {
Ok(handle_editor_input_event(
editor,
terminal,
event,
char_filter,
))
}
}

View file

@ -1,5 +1,5 @@
use std::iter;
use std::sync::Arc; use std::sync::Arc;
use std::{io, iter};
use async_trait::async_trait; use async_trait::async_trait;
use crossterm::style::{ContentStyle, Stylize}; use crossterm::style::{ContentStyle, Stylize};
@ -404,15 +404,30 @@ impl EditorState {
self.0.lock().move_cursor_down(frame); self.0.lock().move_cursor_down(frame);
} }
pub fn edit_externally(&self, terminal: &mut Terminal, crossterm_lock: &Arc<FairMutex<()>>) { pub fn edit_externally(
&self,
terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>,
) -> io::Result<()> {
let mut guard = self.0.lock(); let mut guard = self.0.lock();
if let Some(text) = util::prompt(terminal, crossterm_lock, &guard.text) { let text = util::prompt(terminal, crossterm_lock, &guard.text)?;
if text.trim().is_empty() {
// The user likely wanted to abort the edit and has deleted the
// entire text (bar whitespace left over by some editors).
return Ok(());
}
if let Some(text) = text.strip_suffix('\n') { if let Some(text) = text.strip_suffix('\n') {
// Some editors like vim add a trailing newline that would look out
// of place in cove's editor. To intentionally add a trailing
// newline, simply add two in-editor.
guard.set_text(terminal.frame(), text.to_string()); guard.set_text(terminal.frame(), text.to_string());
} else { } else {
guard.set_text(terminal.frame(), text); guard.set_text(terminal.frame(), text);
} }
}
Ok(())
} }
} }