Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(term): abstract interactor #121

Merged
merged 1 commit into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/synd_term/src/application/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
application::{Application, Authenticator, Cache, Clock, Config},
client::{github::GithubClient, Client},
config::Categories,
interact::Interactor,
interact::Interact,
terminal::Terminal,
ui::theme::Theme,
};
Expand All @@ -23,7 +23,7 @@ pub struct ApplicationBuilder<
pub(super) theme: Theme,

pub(super) authenticator: Option<Authenticator>,
pub(super) interactor: Option<Interactor>,
pub(super) interactor: Option<Box<dyn Interact>>,
pub(super) github_client: Option<GithubClient>,
pub(super) clock: Option<Box<dyn Clock>>,
pub(super) dry_run: bool,
Expand Down Expand Up @@ -174,7 +174,7 @@ impl<T1, T2, T3, T4, T5, T6> ApplicationBuilder<T1, T2, T3, T4, T5, T6> {
}

#[must_use]
pub fn interactor(self, interactor: Interactor) -> Self {
pub fn interactor(self, interactor: Box<dyn Interact>) -> Self {
Self {
interactor: Some(interactor),
..self
Expand Down
56 changes: 44 additions & 12 deletions crates/synd_term/src/application/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use synd_auth::device_flow::DeviceAuthorizationResponse;
use synd_feed::types::FeedUrl;
use tokio::time::{Instant, Sleep};
use update_informer::Version;
use url::Url;

use crate::{
application::event::KeyEventResult,
Expand All @@ -29,7 +30,7 @@ use crate::{
},
command::{ApiResponse, Command},
config::{self, Categories},
interact::Interactor,
interact::{Interact, ProcessInteractor},
job::Jobs,
keymap::{KeymapId, Keymaps},
terminal::Terminal,
Expand Down Expand Up @@ -87,7 +88,7 @@ pub struct Application {
jobs: Jobs,
background_jobs: Jobs,
components: Components,
interactor: Interactor,
interactor: Box<dyn Interact>,
authenticator: Authenticator,
in_flight: InFlight,
cache: Cache,
Expand Down Expand Up @@ -149,7 +150,7 @@ impl Application {
jobs: Jobs::new(NonZero::new(90).unwrap()),
background_jobs: Jobs::new(NonZero::new(10).unwrap()),
components: Components::new(&config.features),
interactor: interactor.unwrap_or_else(Interactor::new),
interactor: interactor.unwrap_or_else(|| Box::new(ProcessInteractor::new())),
authenticator: authenticator.unwrap_or_else(Authenticator::new),
in_flight: InFlight::new().with_throbber_timer_interval(config.throbber_timer_interval),
cache,
Expand Down Expand Up @@ -898,9 +899,17 @@ impl Application {

impl Application {
fn prompt_feed_subscription(&mut self) {
let input = self
let input = match self
.interactor
.open_editor(InputParser::SUSBSCRIBE_FEED_PROMPT);
.open_editor(InputParser::SUSBSCRIBE_FEED_PROMPT)
{
Ok(input) => input,
Err(err) => {
tracing::warn!("{err}");
// TODO: Handle error case
return;
}
};
tracing::debug!("Got user modified feed subscription: {input}");
// the terminal state becomes strange after editing in the editor
self.terminal.force_redraw();
Expand Down Expand Up @@ -936,9 +945,17 @@ impl Application {
return;
};

let input = self
let input = match self
.interactor
.open_editor(InputParser::edit_feed_prompt(feed));
.open_editor(InputParser::edit_feed_prompt(feed).as_str())
{
Ok(input) => input,
Err(err) => {
// TODO: handle error case
tracing::warn!("{err}");
return;
}
};
// the terminal state becomes strange after editing in the editor
self.terminal.force_redraw();

Expand Down Expand Up @@ -1071,14 +1088,28 @@ impl Application {
else {
return;
};
self.interactor.open_browser(feed_website_url);
match Url::parse(&feed_website_url) {
Ok(url) => {
self.interactor.open_browser(url).ok();
}
Err(err) => {
tracing::warn!("Try to open invalid feed url: {feed_website_url} {err}");
}
};
}

fn open_entry(&mut self) {
let Some(entry_website_url) = self.components.entries.selected_entry_website_url() else {
return;
};
self.interactor.open_browser(entry_website_url);
match Url::parse(entry_website_url) {
Ok(url) => {
self.interactor.open_browser(url).ok();
}
Err(err) => {
tracing::warn!("Try to open invalid entry url: {entry_website_url} {err}");
}
};
}

fn open_notification(&mut self) {
Expand All @@ -1090,7 +1121,7 @@ impl Application {
else {
return;
};
self.interactor.open_browser(notification_url.as_str());
self.interactor.open_browser(notification_url).ok();
}
}

Expand Down Expand Up @@ -1264,8 +1295,9 @@ impl Application {
.set_device_authorization_response(device_authorization.clone());
self.should_render();
// try to open input screen in the browser
self.interactor
.open_browser(device_authorization.verification_uri().to_string());
if let Ok(url) = Url::parse(device_authorization.verification_uri().to_string().as_str()) {
self.interactor.open_browser(url).ok();
}

let authenticator = self.authenticator.clone();
let now = self.now();
Expand Down
34 changes: 0 additions & 34 deletions crates/synd_term/src/interact/integration_interactor.rs

This file was deleted.

18 changes: 0 additions & 18 deletions crates/synd_term/src/interact/interactor.rs

This file was deleted.

38 changes: 38 additions & 0 deletions crates/synd_term/src/interact/mock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use std::cell::RefCell;

use crate::interact::{Interact, OpenBrowser, OpenBrowserError, OpenEditor, OpenEditorError};

pub struct MockInteractor {
editor_buffer: RefCell<Vec<String>>,
browser_urls: RefCell<Vec<String>>,
}

impl MockInteractor {
pub fn new() -> Self {
Self {
editor_buffer: RefCell::new(Vec::new()),
browser_urls: RefCell::new(Vec::new()),
}
}

#[must_use]
pub fn with_buffer(mut self, editor_buffer: Vec<String>) -> Self {
self.editor_buffer = RefCell::new(editor_buffer);
self
}
}

impl OpenBrowser for MockInteractor {
fn open_browser(&self, url: url::Url) -> Result<(), OpenBrowserError> {
self.browser_urls.borrow_mut().push(url.to_string());
Ok(())
}
}

impl OpenEditor for MockInteractor {
fn open_editor(&self, _initial_content: &str) -> Result<String, OpenEditorError> {
Ok(self.editor_buffer.borrow_mut().remove(0))
}
}

impl Interact for MockInteractor {}
36 changes: 29 additions & 7 deletions crates/synd_term/src/interact/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
#[cfg(not(feature = "integration"))]
mod interactor;
#[cfg(not(feature = "integration"))]
pub use interactor::Interactor;
use std::io;

#[cfg(feature = "integration")]
mod integration_interactor;
#[cfg(feature = "integration")]
pub use integration_interactor::Interactor;
pub mod mock;
mod process;
pub use process::ProcessInteractor;

use thiserror::Error;
use url::Url;

pub trait Interact: OpenBrowser + OpenEditor {}

#[derive(Debug, Error)]
pub enum OpenBrowserError {
#[error("failed to open browser: {0}")]
Io(#[from] io::Error),
}

pub trait OpenBrowser {
fn open_browser(&self, url: Url) -> Result<(), OpenBrowserError>;
}

#[derive(Debug, Error)]
#[error("failed to open editor: {message}")]
pub struct OpenEditorError {
message: String,
}

pub trait OpenEditor {
fn open_editor(&self, initial_content: &str) -> Result<String, OpenEditorError>;
}
25 changes: 25 additions & 0 deletions crates/synd_term/src/interact/process.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use crate::interact::{Interact, OpenBrowser, OpenBrowserError, OpenEditor};

pub struct ProcessInteractor {}

impl ProcessInteractor {
pub fn new() -> Self {
Self {}
}
}

impl OpenBrowser for ProcessInteractor {
fn open_browser(&self, url: url::Url) -> Result<(), super::OpenBrowserError> {
open::that(url.as_str()).map_err(OpenBrowserError::from)
}
}

impl OpenEditor for ProcessInteractor {
fn open_editor(&self, initial_content: &str) -> Result<String, super::OpenEditorError> {
edit::edit(initial_content).map_err(|err| super::OpenEditorError {
message: err.to_string(),
})
}
}

impl Interact for ProcessInteractor {}
4 changes: 2 additions & 2 deletions crates/synd_term/tests/test/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use synd_term::{
auth::Credential,
client::{github::GithubClient as TermGithubClient, Client},
config::Categories,
interact::Interactor,
interact::mock::MockInteractor,
terminal::Terminal,
types::Time,
ui::theme::Theme,
Expand Down Expand Up @@ -190,7 +190,7 @@ impl TestCase {
} else {
Vec::new()
};
Interactor::new().with_buffer(buffer)
Box::new(MockInteractor::new().with_buffer(buffer))
};

let github_client = {
Expand Down