~mjboa: 2 Refactor history, add going forward Add forward button 4 files changed, 266 insertions(+), 64 deletions(-)
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~julienxx/castor/patches/11848/mbox | git am -3Learn more about email & git
From: Michael Beaumont <mjboamail@gmail.com> --- src/history.rs | 214 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 181 insertions(+), 33 deletions(-) diff --git a/src/history.rs b/src/history.rs index 0dd0ce8..304aa2a 100644 --- a/src/history.rs +++ b/src/history.rs @@ -2,56 +2,204 @@ use std::sync::Mutex; use url::Url; lazy_static! { - static ref HISTORY: Mutex<Vec<Url>> = Mutex::new(vec![]); + static ref HISTORY: Mutex<History> = Mutex::new(History::new()); +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct History { + past: Vec<Url>, + current: Option<Url>, + future: Vec<Url>, +} + +impl History { + fn new() -> Self { + History { + past: vec![], + current: None, + future: vec![], + } + } + + fn get_previous_url(&mut self) -> Option<Url> { + let p = self.past.pop(); + if p.is_some() { + if let Some(c) = self.current.take() { + self.future.push(c); + } + self.current = None; + }; + p + } + + fn get_next_url(&mut self) -> Option<Url> { + let f = self.future.pop(); + if f.is_some() { + if let Some(c) = self.current.take() { + self.past.push(c); + } + self.current = None; + }; + f + } + + fn append(&mut self, url: &str) { + let url = Url::parse(url).unwrap(); + if let Some(c) = self.current.replace(url) { + self.past.push(c); + self.future = vec![] + }; + } + + fn current(&self) -> Option<&Url> { + self.current.as_ref() + } } pub fn append(url: &str) { - let url = Url::parse(url).unwrap(); - HISTORY.lock().unwrap().push(url) + HISTORY.lock().unwrap().append(url) } pub fn get_current_host() -> Option<String> { - let history = HISTORY.lock().unwrap(); - match history.last() { - Some(current_url) => match current_url.host_str() { - Some(host) => Some(String::from(host)), - None => None, - }, - None => None, - } + HISTORY + .lock() + .unwrap() + .current() + .and_then(|u| u.host_str()) + .map(String::from) } pub fn get_current_url() -> Option<String> { - let history = HISTORY.lock().unwrap(); - match history.last() { - Some(current_url) => { - let current_path = current_url.join("./"); - match current_path { - Ok(path) => Some(path.to_string()), - Err(_) => None, - } - } - None => None, - } + HISTORY + .lock() + .unwrap() + .current() + .and_then(|u| u.join("./").ok()) + .map(|p| p.to_string()) } pub fn get_previous_url() -> Option<Url> { let mut history = HISTORY.lock().unwrap(); - if history.len() > 1 { - history.pop(); // remove current + history.get_previous_url() +} - if history.len() > 1 { - history.pop() // return previous - } else { - history.iter().cloned().last() - } - } else { - None - } +pub fn get_next_url() -> Option<Url> { + let mut history = HISTORY.lock().unwrap(); + + history.get_next_url() } #[cfg(test)] pub(crate) fn clear() -> () { - HISTORY.lock().unwrap().clear(); + *HISTORY.lock().unwrap() = History::new(); +} + +#[test] +fn test_append_simple() { + crate::history::clear(); + + *HISTORY.lock().unwrap() = History { + past: vec![], + current: Some(Url::parse("gemini://typed-hole.org/foo").unwrap()), + future: vec![], + }; + + append("gemini://typed-hole.org"); + + assert_eq!( + *HISTORY.lock().unwrap(), + History { + past: vec![Url::parse("gemini://typed-hole.org/foo").unwrap()], + current: Some(Url::parse("gemini://typed-hole.org").unwrap()), + future: vec![], + }, + ); +} + +#[test] +fn test_append_clear_future() { + crate::history::clear(); + + *HISTORY.lock().unwrap() = History { + past: vec![], + current: Some(Url::parse("gemini://typed-hole.org").unwrap()), + future: vec![Url::parse("gemini://typed-hole.org/foo").unwrap()], + }; + + append("gemini://typed-hole.org/bar"); + + assert_eq!( + *HISTORY.lock().unwrap(), + History { + past: vec![Url::parse("gemini://typed-hole.org").unwrap()], + current: Some(Url::parse("gemini://typed-hole.org/bar").unwrap()), + future: vec![], + }, + ); +} + +#[test] +fn test_append_no_current() { + crate::history::clear(); + + *HISTORY.lock().unwrap() = History { + past: vec![], + current: None, + future: vec![Url::parse("gemini://typed-hole.org/foo").unwrap()], + }; + + append("gemini://typed-hole.org"); + + assert_eq!( + *HISTORY.lock().unwrap(), + History { + past: vec![], + current: Some(Url::parse("gemini://typed-hole.org").unwrap()), + future: vec![Url::parse("gemini://typed-hole.org/foo").unwrap()], + }, + ); +} + +#[test] +fn test_get_previous_url_simple() { + crate::history::clear(); + + *HISTORY.lock().unwrap() = History { + past: vec![Url::parse("gemini://typed-hole.org").unwrap()], + current: Some(Url::parse("gemini://typed-hole.org/foo").unwrap()), + future: vec![], + }; + + let previous = get_previous_url(); + + assert_eq!( + previous, + Some(Url::parse("gemini://typed-hole.org").unwrap()) + ); + assert_eq!( + *HISTORY.lock().unwrap(), + History { + past: vec![], + current: None, + future: vec![Url::parse("gemini://typed-hole.org/foo").unwrap()] + }, + ); +} + +#[test] +fn test_get_previous_url_no_past() { + crate::history::clear(); + + let simple = History { + past: vec![], + current: Some(Url::parse("gemini://typed-hole.org/foo").unwrap()), + future: vec![], + }; + *HISTORY.lock().unwrap() = simple.clone(); + + let previous = get_previous_url(); + + assert_eq!(previous, None); + assert_eq!(*HISTORY.lock().unwrap(), simple); } -- 2.26.2
Thanks a lot for the patch and sorry it took me so long to apply it.
From: Michael Beaumont <mjboamail@gmail.com> --- src/castor.glade | 39 ++++++++++++++++++++++----- src/gui.rs | 9 +++++++ src/main.rs | 68 ++++++++++++++++++++++++++++++------------------ 3 files changed, 85 insertions(+), 31 deletions(-) diff --git a/src/castor.glade b/src/castor.glade index 3c32605..323cb49 100644 --- a/src/castor.glade +++ b/src/castor.glade @@ -22,6 +22,11 @@ <property name="stock">gtk-go-back</property> </object> <object class="GtkImage" id="image4"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="stock">gtk-go-forward</property> + </object> + <object class="GtkImage" id="image5"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="stock">gtk-refresh</property> @@ -67,6 +72,28 @@ <property name="position">0</property> </packing> </child> + <child> + <object class="GtkButton" id="forward_button"> + <property name="name">forward_button</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="tooltip_text" translatable="yes">Go forward</property> + <property name="margin_right">1</property> + <property name="margin_top">15</property> + <property name="margin_bottom">15</property> + <property name="image">image4</property> + <property name="always_show_image">True</property> + <accelerator key="Left" signal="clicked" modifiers="GDK_MOD1_MASK"/> + <accelerator key="Back" signal="clicked"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">1</property> + <property name="position">1</property> + </packing> + </child> <child> <object class="GtkButton" id="refresh_button"> <property name="name">refresh_button</property> @@ -74,10 +101,10 @@ <property name="can_focus">True</property> <property name="receives_default">True</property> <property name="tooltip_text" translatable="yes">Refresh</property> - <property name="margin_right">1</property> + <property name="margin_left">6</property> <property name="margin_top">15</property> <property name="margin_bottom">15</property> - <property name="image">image4</property> + <property name="image">image5</property> <property name="always_show_image">True</property> <accelerator key="r" signal="clicked" modifiers="GDK_CONTROL_MASK"/> </object> @@ -85,7 +112,7 @@ <property name="expand">False</property> <property name="fill">True</property> <property name="padding">1</property> - <property name="position">1</property> + <property name="position">2</property> </packing> </child> <child> @@ -105,7 +132,7 @@ <property name="expand">True</property> <property name="fill">True</property> <property name="padding">1</property> - <property name="position">2</property> + <property name="position">3</property> </packing> </child> <child> @@ -125,7 +152,7 @@ <property name="expand">False</property> <property name="fill">True</property> <property name="padding">1</property> - <property name="position">3</property> + <property name="position">4</property> </packing> </child> <child> @@ -145,7 +172,7 @@ <property name="expand">False</property> <property name="fill">True</property> <property name="padding">1</property> - <property name="position">4</property> + <property name="position">5</property> </packing> </child> </object> diff --git a/src/gui.rs b/src/gui.rs index eb26e1e..5b896c9 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -8,6 +8,7 @@ pub struct Gui { url_bar: Entry, content_view: TextView, back_button: Button, + forward_button: Button, refresh_button: Button, add_bookmark_button: Button, show_bookmarks_button: Button, @@ -34,6 +35,9 @@ impl Gui { let back_button: Button = builder .get_object("back_button") .expect("Couldn't get back_button"); + let forward_button: Button = builder + .get_object("forward_button") + .expect("Couldn't get forward_button"); let refresh_button: Button = builder .get_object("refresh_button") .expect("Couldn't get refresh_button"); @@ -49,6 +53,7 @@ impl Gui { url_bar, content_view, back_button, + forward_button, refresh_button, add_bookmark_button, show_bookmarks_button, @@ -86,6 +91,10 @@ impl Gui { &self.back_button } + pub fn forward_button(&self) -> &Button { + &self.forward_button + } + pub fn refresh_button(&self) -> &Button { &self.refresh_button } diff --git a/src/main.rs b/src/main.rs index 456836f..bcbc7c3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ extern crate lazy_static; use std::env; use std::str::FromStr; use std::sync::Arc; +use url::Url; use gtk::prelude::*; @@ -60,6 +61,15 @@ fn main() { }); } + // Bind forward button + { + let button = gui.forward_button(); + let gui = gui.clone(); + button.connect_clicked(move |_| { + go_forward(&gui); + }); + } + // Bind refresh button { let button = gui.refresh_button(); @@ -145,29 +155,38 @@ fn route_url(gui: &Arc<Gui>, url: String) { } fn go_back(gui: &Arc<Gui>) { - let previous = history::get_previous_url(); - if let Some(url) = previous { - match url.scheme() { - "finger" => visit_url( - gui, - Finger { - source: url.to_string(), - }, - ), - "gemini" => visit_url( - gui, - Gemini { - source: url.to_string(), - }, - ), - "gopher" => visit_url( - gui, - Gopher { - source: url.to_string(), - }, - ), - _ => (), - } + if let Some(prev) = history::get_previous_url() { + visit(gui, &prev); + } +} + +fn go_forward(gui: &Arc<Gui>) { + if let Some(next) = history::get_next_url() { + visit(gui, &next); + } +} + +fn visit(gui: &Arc<Gui>, url: &Url) { + match url.scheme() { + "finger" => visit_url( + gui, + Finger { + source: url.to_string(), + }, + ), + "gemini" => visit_url( + gui, + Gemini { + source: url.to_string(), + }, + ), + "gopher" => visit_url( + gui, + Gopher { + source: url.to_string(), + }, + ), + _ => (), } } @@ -190,8 +209,7 @@ fn add_bookmark(gui: &Arc<Gui>) { if bookmarks::is_valid(&url) { bookmarks::add(&url); dialog::info(&gui, "Bookmark added."); - } - else { + } else { dialog::error(&gui, "Invalid bookmark URL."); } } -- 2.26.2