Skip to content

Commit f0283f8

Browse files
authored
refactor(term): abstract interactor (#121)
1 parent df2c818 commit f0283f8

File tree

8 files changed

+141
-76
lines changed

8 files changed

+141
-76
lines changed

crates/synd_term/src/application/builder.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::{
22
application::{Application, Authenticator, Cache, Clock, Config},
33
client::{github::GithubClient, Client},
44
config::Categories,
5-
interact::Interactor,
5+
interact::Interact,
66
terminal::Terminal,
77
ui::theme::Theme,
88
};
@@ -23,7 +23,7 @@ pub struct ApplicationBuilder<
2323
pub(super) theme: Theme,
2424

2525
pub(super) authenticator: Option<Authenticator>,
26-
pub(super) interactor: Option<Interactor>,
26+
pub(super) interactor: Option<Box<dyn Interact>>,
2727
pub(super) github_client: Option<GithubClient>,
2828
pub(super) clock: Option<Box<dyn Clock>>,
2929
pub(super) dry_run: bool,
@@ -174,7 +174,7 @@ impl<T1, T2, T3, T4, T5, T6> ApplicationBuilder<T1, T2, T3, T4, T5, T6> {
174174
}
175175

176176
#[must_use]
177-
pub fn interactor(self, interactor: Interactor) -> Self {
177+
pub fn interactor(self, interactor: Box<dyn Interact>) -> Self {
178178
Self {
179179
interactor: Some(interactor),
180180
..self

crates/synd_term/src/application/mod.rs

+44-12
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use synd_auth::device_flow::DeviceAuthorizationResponse;
1818
use synd_feed::types::FeedUrl;
1919
use tokio::time::{Instant, Sleep};
2020
use update_informer::Version;
21+
use url::Url;
2122

2223
use crate::{
2324
application::event::KeyEventResult,
@@ -29,7 +30,7 @@ use crate::{
2930
},
3031
command::{ApiResponse, Command},
3132
config::{self, Categories},
32-
interact::Interactor,
33+
interact::{Interact, ProcessInteractor},
3334
job::Jobs,
3435
keymap::{KeymapId, Keymaps},
3536
terminal::Terminal,
@@ -87,7 +88,7 @@ pub struct Application {
8788
jobs: Jobs,
8889
background_jobs: Jobs,
8990
components: Components,
90-
interactor: Interactor,
91+
interactor: Box<dyn Interact>,
9192
authenticator: Authenticator,
9293
in_flight: InFlight,
9394
cache: Cache,
@@ -149,7 +150,7 @@ impl Application {
149150
jobs: Jobs::new(NonZero::new(90).unwrap()),
150151
background_jobs: Jobs::new(NonZero::new(10).unwrap()),
151152
components: Components::new(&config.features),
152-
interactor: interactor.unwrap_or_else(Interactor::new),
153+
interactor: interactor.unwrap_or_else(|| Box::new(ProcessInteractor::new())),
153154
authenticator: authenticator.unwrap_or_else(Authenticator::new),
154155
in_flight: InFlight::new().with_throbber_timer_interval(config.throbber_timer_interval),
155156
cache,
@@ -898,9 +899,17 @@ impl Application {
898899

899900
impl Application {
900901
fn prompt_feed_subscription(&mut self) {
901-
let input = self
902+
let input = match self
902903
.interactor
903-
.open_editor(InputParser::SUSBSCRIBE_FEED_PROMPT);
904+
.open_editor(InputParser::SUSBSCRIBE_FEED_PROMPT)
905+
{
906+
Ok(input) => input,
907+
Err(err) => {
908+
tracing::warn!("{err}");
909+
// TODO: Handle error case
910+
return;
911+
}
912+
};
904913
tracing::debug!("Got user modified feed subscription: {input}");
905914
// the terminal state becomes strange after editing in the editor
906915
self.terminal.force_redraw();
@@ -936,9 +945,17 @@ impl Application {
936945
return;
937946
};
938947

939-
let input = self
948+
let input = match self
940949
.interactor
941-
.open_editor(InputParser::edit_feed_prompt(feed));
950+
.open_editor(InputParser::edit_feed_prompt(feed).as_str())
951+
{
952+
Ok(input) => input,
953+
Err(err) => {
954+
// TODO: handle error case
955+
tracing::warn!("{err}");
956+
return;
957+
}
958+
};
942959
// the terminal state becomes strange after editing in the editor
943960
self.terminal.force_redraw();
944961

@@ -1071,14 +1088,28 @@ impl Application {
10711088
else {
10721089
return;
10731090
};
1074-
self.interactor.open_browser(feed_website_url);
1091+
match Url::parse(&feed_website_url) {
1092+
Ok(url) => {
1093+
self.interactor.open_browser(url).ok();
1094+
}
1095+
Err(err) => {
1096+
tracing::warn!("Try to open invalid feed url: {feed_website_url} {err}");
1097+
}
1098+
};
10751099
}
10761100

10771101
fn open_entry(&mut self) {
10781102
let Some(entry_website_url) = self.components.entries.selected_entry_website_url() else {
10791103
return;
10801104
};
1081-
self.interactor.open_browser(entry_website_url);
1105+
match Url::parse(entry_website_url) {
1106+
Ok(url) => {
1107+
self.interactor.open_browser(url).ok();
1108+
}
1109+
Err(err) => {
1110+
tracing::warn!("Try to open invalid entry url: {entry_website_url} {err}");
1111+
}
1112+
};
10821113
}
10831114

10841115
fn open_notification(&mut self) {
@@ -1090,7 +1121,7 @@ impl Application {
10901121
else {
10911122
return;
10921123
};
1093-
self.interactor.open_browser(notification_url.as_str());
1124+
self.interactor.open_browser(notification_url).ok();
10941125
}
10951126
}
10961127

@@ -1264,8 +1295,9 @@ impl Application {
12641295
.set_device_authorization_response(device_authorization.clone());
12651296
self.should_render();
12661297
// try to open input screen in the browser
1267-
self.interactor
1268-
.open_browser(device_authorization.verification_uri().to_string());
1298+
if let Ok(url) = Url::parse(device_authorization.verification_uri().to_string().as_str()) {
1299+
self.interactor.open_browser(url).ok();
1300+
}
12691301

12701302
let authenticator = self.authenticator.clone();
12711303
let now = self.now();

crates/synd_term/src/interact/integration_interactor.rs

-34
This file was deleted.

crates/synd_term/src/interact/interactor.rs

-18
This file was deleted.

crates/synd_term/src/interact/mock.rs

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use std::cell::RefCell;
2+
3+
use crate::interact::{Interact, OpenBrowser, OpenBrowserError, OpenEditor, OpenEditorError};
4+
5+
pub struct MockInteractor {
6+
editor_buffer: RefCell<Vec<String>>,
7+
browser_urls: RefCell<Vec<String>>,
8+
}
9+
10+
impl MockInteractor {
11+
pub fn new() -> Self {
12+
Self {
13+
editor_buffer: RefCell::new(Vec::new()),
14+
browser_urls: RefCell::new(Vec::new()),
15+
}
16+
}
17+
18+
#[must_use]
19+
pub fn with_buffer(mut self, editor_buffer: Vec<String>) -> Self {
20+
self.editor_buffer = RefCell::new(editor_buffer);
21+
self
22+
}
23+
}
24+
25+
impl OpenBrowser for MockInteractor {
26+
fn open_browser(&self, url: url::Url) -> Result<(), OpenBrowserError> {
27+
self.browser_urls.borrow_mut().push(url.to_string());
28+
Ok(())
29+
}
30+
}
31+
32+
impl OpenEditor for MockInteractor {
33+
fn open_editor(&self, _initial_content: &str) -> Result<String, OpenEditorError> {
34+
Ok(self.editor_buffer.borrow_mut().remove(0))
35+
}
36+
}
37+
38+
impl Interact for MockInteractor {}

crates/synd_term/src/interact/mod.rs

+29-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,31 @@
1-
#[cfg(not(feature = "integration"))]
2-
mod interactor;
3-
#[cfg(not(feature = "integration"))]
4-
pub use interactor::Interactor;
1+
use std::io;
52

63
#[cfg(feature = "integration")]
7-
mod integration_interactor;
8-
#[cfg(feature = "integration")]
9-
pub use integration_interactor::Interactor;
4+
pub mod mock;
5+
mod process;
6+
pub use process::ProcessInteractor;
7+
8+
use thiserror::Error;
9+
use url::Url;
10+
11+
pub trait Interact: OpenBrowser + OpenEditor {}
12+
13+
#[derive(Debug, Error)]
14+
pub enum OpenBrowserError {
15+
#[error("failed to open browser: {0}")]
16+
Io(#[from] io::Error),
17+
}
18+
19+
pub trait OpenBrowser {
20+
fn open_browser(&self, url: Url) -> Result<(), OpenBrowserError>;
21+
}
22+
23+
#[derive(Debug, Error)]
24+
#[error("failed to open editor: {message}")]
25+
pub struct OpenEditorError {
26+
message: String,
27+
}
28+
29+
pub trait OpenEditor {
30+
fn open_editor(&self, initial_content: &str) -> Result<String, OpenEditorError>;
31+
}
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use crate::interact::{Interact, OpenBrowser, OpenBrowserError, OpenEditor};
2+
3+
pub struct ProcessInteractor {}
4+
5+
impl ProcessInteractor {
6+
pub fn new() -> Self {
7+
Self {}
8+
}
9+
}
10+
11+
impl OpenBrowser for ProcessInteractor {
12+
fn open_browser(&self, url: url::Url) -> Result<(), super::OpenBrowserError> {
13+
open::that(url.as_str()).map_err(OpenBrowserError::from)
14+
}
15+
}
16+
17+
impl OpenEditor for ProcessInteractor {
18+
fn open_editor(&self, initial_content: &str) -> Result<String, super::OpenEditorError> {
19+
edit::edit(initial_content).map_err(|err| super::OpenEditorError {
20+
message: err.to_string(),
21+
})
22+
}
23+
}
24+
25+
impl Interact for ProcessInteractor {}

crates/synd_term/tests/test/helper.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use synd_term::{
2323
auth::Credential,
2424
client::{github::GithubClient as TermGithubClient, Client},
2525
config::Categories,
26-
interact::Interactor,
26+
interact::mock::MockInteractor,
2727
terminal::Terminal,
2828
types::Time,
2929
ui::theme::Theme,
@@ -190,7 +190,7 @@ impl TestCase {
190190
} else {
191191
Vec::new()
192192
};
193-
Interactor::new().with_buffer(buffer)
193+
Box::new(MockInteractor::new().with_buffer(buffer))
194194
};
195195

196196
let github_client = {

0 commit comments

Comments
 (0)