Skip to content

Commit

Permalink
Add shortcut for opening urls in entry
Browse files Browse the repository at this point in the history
  • Loading branch information
nthnd committed Aug 5, 2023
1 parent 1482bb1 commit ca6d12a
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 4 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ ratatui = "0.20"
ureq = "2.6"
webbrowser = "0.8"
wsl = "0.1"
linkify = "0.10.0"

[profile.release]
codegen-units = 1
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ Some normal mode controls vary based on whether you are currently selecting a fe
- `x` - refresh all feeds
- `i` - change to insert mode
- `a` - toggle between read/unread entries
- `c` - copy the selected link to the clipboard (feed or entry)
- `o` - open the selected link in your browser (feed or entry)
- `c` - copy the selected link to the clipboard (feed or entry or references)
- `o` - open the selected link in your browser (feed or entry or references)
- `u` - find referenced/cited urls within current entry

### controls - insert mode

Expand Down
62 changes: 61 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::modes::{Mode, ReadMode, Selected};
use crate::util;
use crate::util::{self, get_referenced_urls_from_text, StatefulList};
use anyhow::Result;
use copypasta::{ClipboardContext, ClipboardProvider};
use crossterm::event::{KeyCode, KeyModifiers};
Expand Down Expand Up @@ -64,6 +64,7 @@ impl App {
(toggle_help, Result<()>),
(toggle_read, Result<()>),
(toggle_read_mode, Result<()>),
(find_references, Result<()>),
(update_current_feed_and_entries, Result<()>),
];

Expand Down Expand Up @@ -130,6 +131,7 @@ impl App {
}
(KeyCode::Char('c'), _) => self.put_current_link_in_clipboard(),
(KeyCode::Char('o'), _) => self.open_link_in_browser(),
(KeyCode::Char('u'), _) => self.find_references(),
_ => Ok(()),
}
}
Expand Down Expand Up @@ -175,6 +177,8 @@ pub struct AppImpl {
pub entries: util::StatefulList<crate::rss::EntryMeta>,
pub entry_selection_position: usize,
pub current_entry_text: String,
pub current_entry_references: util::StatefulList<String>,
pub references_selection_position: usize,
pub entry_scroll_position: u16,
pub entry_lines_len: usize,
pub entry_lines_rendered_len: u16,
Expand Down Expand Up @@ -229,6 +233,8 @@ impl AppImpl {
entry_column_width: 0,
current_entry_meta: None,
current_entry_text: String::new(),
current_entry_references: Vec::new().into(),
references_selection_position: 0,
current_feed: initial_current_feed,
feed_subscription_input: String::new(),
mode: Mode::Normal,
Expand Down Expand Up @@ -454,6 +460,35 @@ impl AppImpl {
Ok(())
}

pub fn find_references(&mut self) -> Result<()> {
match &self.selected {
Selected::Entry(meta) => {
self.selected = Selected::References(meta.clone());
if let Some(Ok(entry)) = self.get_selected_entry() {
let empty_string = String::new();

let entry_html = entry
.content
.as_ref()
.or(entry.description.as_ref())
.or(Some(&empty_string));

self.current_entry_references = StatefulList::with_items(
get_referenced_urls_from_text(entry_html.unwrap()),
);
} else {
self.current_entry_references = StatefulList::with_items(vec![]);
}
}

Selected::References(meta) => {
self.selected = Selected::Entry(meta.clone());
}
_ => {}
}
Ok(())
}

pub fn clear_error_flash(&mut self) {
self.error_flash = vec![];
}
Expand Down Expand Up @@ -514,6 +549,7 @@ impl AppImpl {
self.update_entry_selection_position();
}
}
Selected::References(_) => (),
Selected::Feeds => (),
Selected::None => (),
}
Expand All @@ -530,10 +566,12 @@ impl AppImpl {
match (&self.read_mode, &self.selected) {
(ReadMode::ShowRead, Selected::Feeds) | (ReadMode::ShowRead, Selected::Entries) => {
self.entry_selection_position = 0;
self.references_selection_position = 0;
self.read_mode = ReadMode::ShowUnread
}
(ReadMode::ShowUnread, Selected::Feeds) | (ReadMode::ShowUnread, Selected::Entries) => {
self.entry_selection_position = 0;
self.references_selection_position = 0;
self.read_mode = ReadMode::ShowRead
}
_ => (),
Expand Down Expand Up @@ -562,6 +600,11 @@ impl AppImpl {
.items
.get(self.entry_selection_position)
.and_then(|entry| entry.link.as_deref()),
Selected::References(_) => self
.current_entry_references
.items
.get(self.references_selection_position)
.map(|x| x.as_str()),
Selected::Entry(e) => e.link.as_deref(),
Selected::None => None,
}
Expand Down Expand Up @@ -615,6 +658,7 @@ impl AppImpl {
Selected::Entries
}
}
Selected::References(_) => (),
Selected::None => (),
}

Expand All @@ -634,6 +678,14 @@ impl AppImpl {
self.update_current_entry_meta()?;
}
}
Selected::References(_) => {
if !self.current_entry_references.items.is_empty() {
self.current_entry_references.previous();
self.references_selection_position =
self.current_entry_references.state.selected().unwrap();
self.update_current_entry_meta()?;
}
}
Selected::Entry(_) => {
if let Some(n) = self.entry_scroll_position.checked_sub(1) {
self.entry_scroll_position = n
Expand All @@ -657,6 +709,7 @@ impl AppImpl {
}
Selected::Entries => self.on_enter(),
Selected::Entry(_) => Ok(()),
Selected::References(_) => Ok(()),
Selected::None => Ok(()),
}
}
Expand All @@ -674,6 +727,13 @@ impl AppImpl {
self.update_current_entry_meta()?;
}
}
Selected::References(_) => {
if !self.current_entry_references.items.is_empty() {
self.current_entry_references.next();
self.references_selection_position =
self.current_entry_references.state.selected().unwrap();
}
}
Selected::Entry(_) => {
if let Some(n) = self.entry_scroll_position.checked_add(1) {
self.entry_scroll_position = n
Expand Down
1 change: 1 addition & 0 deletions src/modes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub enum Selected {
Feeds,
Entries,
Entry(crate::rss::EntryMeta),
References(crate::rss::EntryMeta),
None,
}

Expand Down
76 changes: 75 additions & 1 deletion src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::rc::Rc;

use crate::app::AppImpl;
use crate::modes::{Mode, ReadMode, Selected};
use crate::rss::EntryMeta;
use crate::rss::{EntryMeta, Feed};

const PINK: Color = Color::Rgb(255, 150, 167);

Expand All @@ -29,6 +29,9 @@ pub fn draw<B: Backend>(f: &mut Frame<B>, chunks: Rc<[Rect]>, app: &mut AppImpl)
Selected::Entry(_entry_meta) => {
draw_entry(f, chunks[1], app);
}
Selected::References(_) => {
draw_references(f, chunks[1], app);
}
Selected::None => draw_entries(f, chunks[1], app),
}
}
Expand Down Expand Up @@ -287,6 +290,16 @@ where
text.push_str("r - refresh selected feed; x - refresh all feeds\n");
text.push_str("c - copy link; o - open link in browser\n")
}
Selected::References(_) => {
text.push_str("c - copy link; o - open link in browser\n");
text.push_str("u - return to entry\n");
}
Selected::Entry(_) => {
text.push_str("r - mark entry read/un; a - toggle view read/un\n");
text.push_str("c - copy link; o - open link in browser\n");
text.push_str("u - list urls within entry\n")

}
_ => {
text.push_str("r - mark entry read/un; a - toggle view read/un\n");
text.push_str("c - copy link; o - open link in browser\n")
Expand Down Expand Up @@ -394,6 +407,67 @@ where
}
}

fn draw_references<B>(f: &mut Frame<B>, area: Rect, app: &mut AppImpl)
where
B: Backend,
{
let title = match app.current_feed.as_ref() {
Some(Feed {
title: Some(title), ..
}) => format!("References for: {:?}", title),
_ => "References".to_string(),
};

let references = List::new(
app.current_entry_references
.items
.iter()
.map(|reference| ListItem::new(Span::raw(reference)))
.collect::<Vec<ListItem>>(),
)
.block(
Block::default().borders(Borders::ALL).title(Span::styled(
title,
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
)),
);

let references = match app.selected {
Selected::References(_) => references
.highlight_style(Style::default().fg(PINK).add_modifier(Modifier::BOLD))
.highlight_symbol("> "),
_ => references,
};

if !&app.error_flash.is_empty() {
let chunks = Layout::default()
.constraints([Constraint::Percentage(60), Constraint::Percentage(30)].as_ref())
.direction(Direction::Vertical)
.split(area);
{
let error_text = error_text(&app.error_flash);

let block = Block::default().borders(Borders::ALL).title(Span::styled(
"Error - press 'q' to close",
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
));

let error_widget = Paragraph::new(error_text)
.block(block)
.wrap(Wrap { trim: false })
.scroll((0, 0));

f.render_widget(error_widget, chunks[1]);
}
} else {
f.render_stateful_widget(references, area, &mut app.current_entry_references.state);
}
}

fn draw_entry<B>(f: &mut Frame<B>, area: Rect, app: &mut AppImpl)
where
B: Backend,
Expand Down
17 changes: 17 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use linkify::{LinkFinder, LinkKind};
use ratatui::widgets::ListState;
use std::sync::OnceLock;

#[derive(Debug)]
pub struct StatefulList<T> {
Expand Down Expand Up @@ -57,6 +59,21 @@ impl<T> From<Vec<T>> for StatefulList<T> {
}
}

pub static LINK_FINDER: OnceLock<LinkFinder> = OnceLock::new();

pub(crate) fn get_referenced_urls_from_text(text: &str) -> Vec<String> {
let finder = LINK_FINDER.get_or_init(|| {
let mut lf = LinkFinder::new();
lf.kinds(&[LinkKind::Url]);
lf
});

finder
.links(text)
.map(|link| link.as_str().to_string())
.collect()
}

#[cfg(target_os = "linux")]
pub(crate) fn set_wsl_clipboard_contents(s: &str) -> anyhow::Result<()> {
use std::{
Expand Down

0 comments on commit ca6d12a

Please sign in to comment.