From f5fd051a2c46103548fd2307f31b9eb853119f72 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Fri, 11 Nov 2022 15:09:28 +0200 Subject: [PATCH] WIP --- src/components/mail/view.rs | 168 +++++++++++++++++++++++----- src/components/utilities/dialogs.rs | 130 +++++++++++++++++++++ src/components/utilities/widgets.rs | 1 + src/conf/shortcuts.rs | 2 + src/conf/terminal.rs | 4 + src/state.rs | 1 + 6 files changed, 275 insertions(+), 31 deletions(-) diff --git a/src/components/mail/view.rs b/src/components/mail/view.rs index e7ec7a7b..79023071 100644 --- a/src/components/mail/view.rs +++ b/src/components/mail/view.rs @@ -2294,7 +2294,9 @@ impl Component for MailView { UIEvent::Input(ref key) if !self.cmd_buf.is_empty() && self.mode == ViewMode::Url - && shortcut!(key == shortcuts[MailView::DESCRIPTION]["go_to_url"]) => + && (shortcut!(key == shortcuts[MailView::DESCRIPTION]["go_to_url"]) + || shortcut!(key == shortcuts[MailView::DESCRIPTION]["copy_url"]) + || shortcut!(key == shortcuts[MailView::DESCRIPTION]["pipe_url"])) => { let lidx = self.cmd_buf.parse::().unwrap(); self.cmd_buf.clear(); @@ -2330,40 +2332,144 @@ impl Component for MailView { return true; } }; - - let url_launcher = mailbox_settings!( - context[self.coordinates.0][&self.coordinates.1] - .pager - .url_launcher - ) - .as_ref() - .map(|s| s.as_str()) - .unwrap_or( - #[cfg(target_os = "macos")] + eprintln!("key is {:?}", key); + eprintln!( + "shotrctu key is {:?}", + shortcuts[MailView::DESCRIPTION]["pipe_url"] + ); + if shortcut!(key == shortcuts[MailView::DESCRIPTION]["go_to_url"]) { + let url_launcher = mailbox_settings!( + context[self.coordinates.0][&self.coordinates.1] + .pager + .url_launcher + ) + .as_ref() + .map(|s| s.as_str()) + .unwrap_or( + #[cfg(target_os = "macos")] + { + "open" + }, + #[cfg(not(target_os = "macos"))] + { + "xdg-open" + }, + ); + match Command::new(url_launcher) + .arg(url) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() { - "open" - }, - #[cfg(not(target_os = "macos"))] + Ok(child) => { + context.children.push(child); + } + Err(err) => { + context.replies.push_back(UIEvent::Notification( + Some(format!("Failed to launch {:?}", url_launcher)), + err.to_string(), + Some(NotificationType::Error(melib::ErrorKind::External)), + )); + } + } + } else if shortcut!(key == shortcuts[MailView::DESCRIPTION]["copy_url"]) { + let pipe_to_clipboard = context + .settings + .terminal + .pipe_to_clipboard + .as_ref() + .map(|s| s.as_str()) + .unwrap_or( + #[cfg(target_os = "macos")] + { + "pbcopy" + }, + #[cfg(not(target_os = "macos"))] + { + "xclip -selection clipboard" + }, + ); + match Command::new(pipe_to_clipboard) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() { - "xdg-open" - }, - ); - match Command::new(url_launcher) - .arg(url) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - { - Ok(child) => { - context.children.push(child); + Ok(mut child) => { + let mut stdin = child.stdin.as_mut().unwrap(); + stdin + .write_all(url.as_bytes()) + .chain_err_summary(|| { + format!( + "Failed to write to stdin of `{}`", + pipe_to_clipboard + ) + }) + .unwrap(); + stdin.flush().unwrap(); + context.children.push(child); + } + Err(err) => { + context.replies.push_back(UIEvent::Notification( + Some(format!("Failed to launch {:?}", pipe_to_clipboard)), + err.to_string(), + Some(NotificationType::Error(melib::ErrorKind::External)), + )); + } } - Err(err) => { - context.replies.push_back(UIEvent::Notification( - Some(format!("Failed to launch {:?}", url_launcher)), - err.to_string(), - Some(NotificationType::Error(melib::ErrorKind::External)), - )); + } else if shortcut!(key == shortcuts[MailView::DESCRIPTION]["pipe_url"]) { + let input_window = InputWindow::new("test".into(), context); + //context.replies.push_back(UIEvent::StatusEvent( + // StatusEvent::DisplayMessage(format!("Running input_window")), + //)); + context + .replies + .push_back(UIEvent::GlobalUIDialog(input_window)); + return true; + + /*let pipe_to_clipboard = context + .settings + .terminal + .pipe_to_clipboard + .as_ref() + .map(|s| s.as_str()) + .unwrap_or( + #[cfg(target_os = "macos")] + { + "pbcopy" + }, + #[cfg(not(target_os = "macos"))] + { + "xclip -selection clipboard" + }, + ); + match Command::new(pipe_to_clipboard) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + { + Ok(mut child) => { + let mut stdin = child.stdin.as_mut().unwrap(); + stdin + .write_all(url.as_bytes()) + .chain_err_summary(|| { + format!( + "Failed to write to stdin of `{}`", + pipe_to_clipboard + ) + }) + .unwrap(); + stdin.flush().unwrap(); + context.children.push(child); + } + Err(err) => { + context.replies.push_back(UIEvent::Notification( + Some(format!("Failed to launch {:?}", pipe_to_clipboard)), + err.to_string(), + Some(NotificationType::Error(melib::ErrorKind::External)), + )); + } } + */ } } } diff --git a/src/components/utilities/dialogs.rs b/src/components/utilities/dialogs.rs index 270444cc..ce0272ae 100644 --- a/src/components/utilities/dialogs.rs +++ b/src/components/utilities/dialogs.rs @@ -969,3 +969,133 @@ impl UIConfirmationDialog { }) } } + +pub struct InputWindow { + theme_default: ThemeAttribute, + + vertical_alignment: Alignment, + horizontal_alignment: Alignment, + title: String, + + mode: UIMode, + field: Field, + + /// If true, user has finished their selection + done: bool, + done_fn: + Option Option + 'static + Sync + Send>>, + dirty: bool, + id: ComponentId, +} + +impl InputWindow { + pub fn new(title: String, context: &Context) -> Box { + Box::new(Self { + theme_default: crate::conf::value(context, "theme_default"), + + vertical_alignment: Alignment::Center, + horizontal_alignment: Alignment::Center, + title, + mode: UIMode::Normal, + + field: Field::default(), + + /// If true, user has finished their selection + done: false, + done_fn: Some(Box::new( + |id: ComponentId, result: String| -> Option { + let _ = id; + let _ = result; + None + }, + )), + dirty: true, + id: ComponentId::new_v4(), + }) + } +} + +impl Component for InputWindow { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + std::dbg!(area); + if self.dirty { + let height = height!(area); + if height < 4 { + return; + } + let height = std::cmp::max(4, height / 3); + let dialog_area = align_area( + area, + /* add box perimeter padding */ + pos_inc((width!(area), height), (2, 2)), + /* vertical */ + Alignment::Center, + /* horizontal */ + Alignment::Center, + ); + std::dbg!(dialog_area); + clear_area(grid, dialog_area, self.theme_default); + let inner_area = create_box(grid, dialog_area); + self.field.draw(grid, inner_area, context); + context.dirty_areas.push_back(dialog_area); + self.dirty = false; + } + } + + fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { + match (self.mode, &event) { + (_, UIEvent::Resize) => { + self.set_dirty(true); + false + } + (UIMode::Normal, UIEvent::Input(Key::Char('\n'))) => { + context + .replies + .push_back(UIEvent::ChangeMode(UIMode::Insert)); + true + } + (UIMode::Insert, UIEvent::InsertInput(Key::Esc)) => { + context + .replies + .push_back(UIEvent::ChangeMode(UIMode::Normal)); + true + } + (UIMode::Insert, _) => self.field.process_event(event, context), + _ => false, + } + } + + fn get_shortcuts(&self, context: &Context) -> ShortcutMaps { + let mut map = ShortcutMaps::default(); + map.insert("general", context.settings.shortcuts.general.key_values()); + map + } + + fn is_dirty(&self) -> bool { + self.dirty || self.field.is_dirty() + } + + fn set_dirty(&mut self, value: bool) { + self.dirty = value; + self.field.set_dirty(value); + } + + fn id(&self) -> ComponentId { + self.id + } + fn set_id(&mut self, id: ComponentId) { + self.id = id; + } +} + +impl fmt::Debug for InputWindow { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Display::fmt("InputWindow", f) + } +} + +impl fmt::Display for InputWindow { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Display::fmt("InputWindow", f) + } +} diff --git a/src/components/utilities/widgets.rs b/src/components/utilities/widgets.rs index 682d6b12..6c3f4a0a 100644 --- a/src/components/utilities/widgets.rs +++ b/src/components/utilities/widgets.rs @@ -225,6 +225,7 @@ impl Component for Field { } } } + fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { match *event { UIEvent::InsertInput(Key::Char('\t')) => { diff --git a/src/conf/shortcuts.rs b/src/conf/shortcuts.rs index 45e7cbcf..cedc4b9e 100644 --- a/src/conf/shortcuts.rs +++ b/src/conf/shortcuts.rs @@ -222,6 +222,8 @@ shortcut_key_values! { "envelope-view", add_addresses_to_contacts |> "Select addresses from envelope to add to contacts." |> Key::Char('c'), edit |> "Open envelope in composer." |> Key::Char('e'), go_to_url |> "Go to url of given index" |> Key::Char('g'), + copy_url |> "Copy url of given index to system clipboard" |> Key::Char('G'), + pipe_url |> "Pipe url of given index to specific command" |> Key::Char('w'), open_attachment |> "Opens selected attachment with xdg-open." |> Key::Char('a'), open_mailcap |> "Opens selected attachment according to its mailcap entry." |> Key::Char('m'), reply |> "Reply to envelope." |> Key::Char('R'), diff --git a/src/conf/terminal.rs b/src/conf/terminal.rs index 21c2b31b..b492cc5b 100644 --- a/src/conf/terminal.rs +++ b/src/conf/terminal.rs @@ -52,6 +52,8 @@ pub struct TerminalSettings { /// Default: 0 #[serde(default)] pub progress_spinner_sequence: Option, + #[serde(deserialize_with = "non_empty_string")] + pub pipe_to_clipboard: Option, } impl Default for TerminalSettings { @@ -66,6 +68,7 @@ impl Default for TerminalSettings { window_title: Some("meli".to_string()), file_picker_command: None, progress_spinner_sequence: None, + pipe_to_clipboard: None, } } } @@ -99,6 +102,7 @@ impl DotAddressable for TerminalSettings { "progress_spinner_sequence" => { self.progress_spinner_sequence.lookup(field, tail) } + "pipe_to_clipboard" => self.pipe_to_clipboard.lookup(field, tail), other => Err(MeliError::new(format!( "{} has no field named {}", parent_field, other diff --git a/src/state.rs b/src/state.rs index e72a4fba..cda57c4f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1168,6 +1168,7 @@ impl State { return; } UIEvent::GlobalUIDialog(dialog) => { + eprint!("global ui dialog push"); self.overlay.push(dialog); return; }