summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoscha <joscha@plugh.de>2022-09-25 18:54:56 +0200
committerJoscha <joscha@plugh.de>2022-09-25 18:57:59 +0200
commit9aac9f6fdd66c4f4eee3c3191a1338e504168f44 (patch)
treefac174ad3012db6222be762332ebe217967bb2a6
parent4c7ac31699c6db2dbcb905d44329c572dbb2d68a (diff)
Add error popup when external editor fails
-rw-r--r--CHANGELOG.md1
-rw-r--r--src/ui/chat.rs2
-rw-r--r--src/ui/chat/tree.rs11
-rw-r--r--src/ui/euph/account.rs10
-rw-r--r--src/ui/euph/auth.rs15
-rw-r--r--src/ui/euph/nick.rs15
-rw-r--r--src/ui/euph/room.rs15
-rw-r--r--src/ui/rooms.rs17
-rw-r--r--src/ui/util.rs68
-rw-r--r--src/ui/widgets/editor.rs31
10 files changed, 95 insertions, 90 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6fa35b4..3cffe55 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ Procedure when bumping the version number:
### Added
- Room deletion confirmation popup
- Message inspection popup
+- Error popup when external editor fails
### Fixed
- Cursor being visible through popups
diff --git a/src/ui/chat.rs b/src/ui/chat.rs
index 6d6514f..467307d 100644
--- a/src/ui/chat.rs
+++ b/src/ui/chat.rs
@@ -5,6 +5,7 @@
mod blocks;
mod tree;
+use std::io;
use std::sync::Arc;
use async_trait::async_trait;
@@ -79,6 +80,7 @@ pub enum Reaction<M: Msg> {
parent: Option<M::Id>,
content: String,
},
+ ComposeError(io::Error),
}
impl<M: Msg> Reaction<M> {
diff --git a/src/ui/chat/tree.rs b/src/ui/chat/tree.rs
index 2ea917f..7fdbc5c 100644
--- a/src/ui/chat/tree.rs
+++ b/src/ui/chat/tree.rs
@@ -225,7 +225,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
fn list_editor_key_bindings(&self, bindings: &mut KeyBindingsList) {
bindings.binding("esc", "close editor");
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(
@@ -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,
terminal,
crossterm_lock,
event,
|_| true,
- true,
);
- if !handled {
- return Reaction::NotHandled;
+ match handled {
+ Ok(true) => {}
+ Ok(false) => return Reaction::NotHandled,
+ Err(e) => return Reaction::ComposeError(e),
}
}
}
diff --git a/src/ui/euph/account.rs b/src/ui/euph/account.rs
index fd16546..0a22479 100644
--- a/src/ui/euph/account.rs
+++ b/src/ui/euph/account.rs
@@ -1,9 +1,6 @@
-use std::sync::Arc;
-
use crossterm::style::{ContentStyle, Stylize};
use euphoxide::api::PersonalAccountView;
use euphoxide::conn::Status;
-use parking_lot::FairMutex;
use toss::terminal::Terminal;
use crate::euph::Room;
@@ -133,7 +130,7 @@ impl AccountUiState {
Focus::Password => bindings.binding("enter", "log in"),
}
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"),
}
@@ -142,7 +139,6 @@ impl AccountUiState {
pub fn handle_input_event(
&mut self,
terminal: &mut Terminal,
- crossterm_lock: &Arc<FairMutex<()>>,
event: &InputEvent,
room: &Option<Room>,
) -> EventResult {
@@ -170,10 +166,8 @@ impl AccountUiState {
if util::handle_editor_input_event(
&logged_out.email,
terminal,
- crossterm_lock,
event,
|c| c != '\n',
- false,
) {
EventResult::Handled
} else {
@@ -192,10 +186,8 @@ impl AccountUiState {
if util::handle_editor_input_event(
&logged_out.password,
terminal,
- crossterm_lock,
event,
|c| c != '\n',
- false,
) {
EventResult::Handled
} else {
diff --git a/src/ui/euph/auth.rs b/src/ui/euph/auth.rs
index 46d83e2..b9b72b9 100644
--- a/src/ui/euph/auth.rs
+++ b/src/ui/euph/auth.rs
@@ -1,6 +1,3 @@
-use std::sync::Arc;
-
-use parking_lot::FairMutex;
use toss::terminal::Terminal;
use crate::euph::Room;
@@ -23,7 +20,7 @@ pub fn widget(editor: &EditorState) -> BoxedWidget {
pub fn list_key_bindings(bindings: &mut KeyBindingsList) {
bindings.binding("esc", "abort");
bindings.binding("enter", "authenticate");
- util::list_editor_key_bindings(bindings, |_| true, false);
+ util::list_editor_key_bindings(bindings, |_| true);
}
pub enum EventResult {
@@ -34,7 +31,6 @@ pub enum EventResult {
pub fn handle_input_event(
terminal: &mut Terminal,
- crossterm_lock: &Arc<FairMutex<()>>,
event: &InputEvent,
room: &Option<Room>,
editor: &EditorState,
@@ -48,14 +44,7 @@ pub fn handle_input_event(
EventResult::ResetState
}
_ => {
- if util::handle_editor_input_event(
- editor,
- terminal,
- crossterm_lock,
- event,
- |_| true,
- false,
- ) {
+ if util::handle_editor_input_event(editor, terminal, event, |_| true) {
EventResult::Handled
} else {
EventResult::NotHandled
diff --git a/src/ui/euph/nick.rs b/src/ui/euph/nick.rs
index 24f7178..9237c57 100644
--- a/src/ui/euph/nick.rs
+++ b/src/ui/euph/nick.rs
@@ -1,7 +1,4 @@
-use std::sync::Arc;
-
use euphoxide::conn::Joined;
-use parking_lot::FairMutex;
use toss::styled::Styled;
use toss::terminal::Terminal;
@@ -34,7 +31,7 @@ fn nick_char(c: char) -> bool {
pub fn list_key_bindings(bindings: &mut KeyBindingsList) {
bindings.binding("esc", "abort");
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 {
@@ -45,7 +42,6 @@ pub enum EventResult {
pub fn handle_input_event(
terminal: &mut Terminal,
- crossterm_lock: &Arc<FairMutex<()>>,
event: &InputEvent,
room: &Option<Room>,
editor: &EditorState,
@@ -59,14 +55,7 @@ pub fn handle_input_event(
EventResult::ResetState
}
_ => {
- if util::handle_editor_input_event(
- editor,
- terminal,
- crossterm_lock,
- event,
- nick_char,
- false,
- ) {
+ if util::handle_editor_input_event(editor, terminal, event, nick_char) {
EventResult::Handled
} else {
EventResult::NotHandled
diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs
index 9b738ed..4d0d57a 100644
--- a/src/ui/euph/room.rs
+++ b/src/ui/euph/room.rs
@@ -380,6 +380,13 @@ impl EuphRoom {
}
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 {
@@ -470,8 +477,7 @@ impl EuphRoom {
.await
}
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::Handled => true,
auth::EventResult::ResetState => {
@@ -481,8 +487,7 @@ impl EuphRoom {
}
}
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::Handled => true,
nick::EventResult::ResetState => {
@@ -492,7 +497,7 @@ impl EuphRoom {
}
}
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::Handled => true,
account::EventResult::ResetState => {
diff --git a/src/ui/rooms.rs b/src/ui/rooms.rs
index c349b39..43f8fbe 100644
--- a/src/ui/rooms.rs
+++ b/src/ui/rooms.rs
@@ -352,13 +352,13 @@ impl Rooms {
bindings.heading("Rooms");
bindings.binding("esc", "abort");
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(_, _) => {
bindings.heading("Rooms");
bindings.binding("esc", "abort");
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);
}
}
- _ => {
- return util::handle_editor_input_event(
- ed,
- terminal,
- crossterm_lock,
- event,
- Self::room_char,
- false,
- )
- }
+ _ => return util::handle_editor_input_event(ed, terminal, event, Self::room_char),
},
State::Delete(name, editor) => match event {
key!(Esc) => self.state = State::ShowList,
@@ -492,10 +483,8 @@ impl Rooms {
return util::handle_editor_input_event(
editor,
terminal,
- crossterm_lock,
event,
Self::room_char,
- false,
)
}
},
diff --git a/src/ui/util.rs b/src/ui/util.rs
index 6b58796..a5d6df0 100644
--- a/src/ui/util.rs
+++ b/src/ui/util.rs
@@ -1,3 +1,4 @@
+use std::io;
use std::sync::Arc;
use parking_lot::FairMutex;
@@ -10,7 +11,7 @@ pub fn prompt(
terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>,
initial_text: &str,
-) -> Option<String> {
+) -> io::Result<String> {
let content = {
let _guard = crossterm_lock.lock();
terminal.suspend().expect("could not suspend");
@@ -19,38 +20,23 @@ pub fn prompt(
content
};
- // TODO Don't swipe this error under the rug
- let content = content.ok()?;
-
- if content.trim().is_empty() {
- None
- } else {
- Some(content)
- }
+ content
}
-// TODO List key binding util functions
-
-pub fn list_editor_key_bindings(
+fn list_editor_editing_key_bindings(
bindings: &mut KeyBindingsList,
char_filter: impl Fn(char) -> bool,
- can_edit_externally: bool,
) {
if char_filter('\n') {
bindings.binding("enter+<any modifier>", "insert newline");
}
- // Editing
bindings.binding("ctrl+h, backspace", "delete before cursor");
bindings.binding("ctrl+d, delete", "delete after cursor");
bindings.binding("ctrl+l", "clear editor contents");
- if can_edit_externally {
- bindings.binding("ctrl+x", "edit in external editor");
- }
-
- bindings.empty();
+}
- // Cursor movement
+fn list_editor_cursor_movement_key_bindings(bindings: &mut KeyBindingsList) {
bindings.binding("ctrl+b, ←", "move cursor left");
bindings.binding("ctrl+f, →", "move cursor right");
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");
}
+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(
editor: &EditorState,
terminal: &mut Terminal,
- crossterm_lock: &Arc<FairMutex<()>>,
event: &InputEvent,
char_filter: impl Fn(char) -> bool,
- can_edit_externally: bool,
) -> bool {
match event {
// 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 + 'd') | key!(Delete) => editor.delete(),
key!(Ctrl + 'l') => editor.clear(),
- key!(Ctrl + 'x') if can_edit_externally => editor.edit_externally(terminal, crossterm_lock),
// TODO Key bindings to delete words
// Cursor movement
@@ -112,3 +104,33 @@ pub fn handle_editor_input_event(
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,
+ ))
+ }
+}
diff --git a/src/ui/widgets/editor.rs b/src/ui/widgets/editor.rs
index c90f6ee..1aecd93 100644
--- a/src/ui/widgets/editor.rs
+++ b/src/ui/widgets/editor.rs
@@ -1,5 +1,5 @@
-use std::iter;
use std::sync::Arc;
+use std::{io, iter};
use async_trait::async_trait;
use crossterm::style::{ContentStyle, Stylize};
@@ -404,15 +404,30 @@ impl EditorState {
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();
- if let Some(text) = util::prompt(terminal, crossterm_lock, &guard.text) {
- if let Some(text) = text.strip_suffix('\n') {
- guard.set_text(terminal.frame(), text.to_string());
- } else {
- guard.set_text(terminal.frame(), 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') {
+ // 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());
+ } else {
+ guard.set_text(terminal.frame(), text);
+ }
+
+ Ok(())
}
}