From 68618482177fe407c3c07496251a0528e7b76a4b Mon Sep 17 00:00:00 2001 From: chris west Date: Sun, 13 Nov 2022 14:37:39 -0800 Subject: [PATCH] add `scroll` config option and default to entire screen --- CHANGELOG.md | 2 + doc/phetch.1.md | 3 ++ src/config.rs | 20 ++++++++-- src/menu.rs | 104 +++++++++++++++++++++++++++--------------------- src/text.rs | 47 ++++++++++++++-------- src/ui.rs | 3 -- 6 files changed, 110 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9daf88b..df3ea1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This release adds a few new config options, for your convenience: +- `scroll` controls how many lines to jump by when paging up/down. + If set to 0 (the new default), you'll jump by an entire screen. - `autoplay` controls whether you'll be prompted to play media files or not. By default it's false, but one might find it handy to set to `true` if hosting, say, a Gopher-powered music server. diff --git a/doc/phetch.1.md b/doc/phetch.1.md index 1e06b9d..6c6adee 100644 --- a/doc/phetch.1.md +++ b/doc/phetch.1.md @@ -235,6 +235,9 @@ encoding utf8 # Wrap text at N columns. 0 = off (--wrap) wrap 0 + +# How many lines to page up/down by? 0 = full screen +scroll 0 ``` # MEDIA PLAYER SUPPORT diff --git a/src/config.rs b/src/config.rs index 182aa96..b278485 100644 --- a/src/config.rs +++ b/src/config.rs @@ -55,6 +55,9 @@ encoding utf8 # Wrap text at N columns. 0 = off (--wrap) wrap 0 + +# How many lines to page up/down by? 0 = full screen +scroll 0 "; /// Not all the config options are available in the phetch.conf. We @@ -82,6 +85,8 @@ pub struct Config { pub mode: ui::Mode, /// Column to wrap lines. 0 = off pub wrap: usize, + /// Scroll by how many lines? 0 = full screen + pub scroll: usize, } impl Default for Config { @@ -97,6 +102,7 @@ impl Default for Config { encoding: Encoding::default(), mode: ui::Mode::default(), wrap: 0, + scroll: 0, } } } @@ -173,15 +179,23 @@ fn parse(text: &str) -> Result { )); } } + "scroll" => { + if let Ok(num) = val.parse() { + cfg.scroll = num; + } else { + return Err(error!( + "`scroll` expects a number value on line {}: {}", + linenum, val + )); + } + } "media" => { cfg.media = match val.to_lowercase().as_ref() { "false" | "none" => None, _ => Some(val.into()), } } - "autoplay" => { - cfg.autoplay = to_bool(val)? - } + "autoplay" => cfg.autoplay = to_bool(val)?, "encoding" => { cfg.encoding = Encoding::from_str(val) .map_err(|e| error!("{} on line {}: {:?}", e, linenum, line))?; diff --git a/src/menu.rs b/src/menu.rs index 36ad038..b357aaa 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -10,7 +10,7 @@ use crate::{ config::SharedConfig as Config, gopher::{self, Type}, terminal, - ui::{self, Action, Key, View, MAX_COLS, SCROLL_LINES}, + ui::{self, Action, Key, View, MAX_COLS}, }; use std::fmt; @@ -38,8 +38,8 @@ pub struct Menu { pub input: String, /// UI mode. Interactive (Run), Printing, Raw mode... pub mode: ui::Mode, - /// Scrolling offset, in rows. - pub scroll: usize, + /// Scrolling offset, in rows. 0 = full screen + pub offset: usize, /// Incremental search mode? pub searching: bool, /// Was this menu retrieved via TLS? @@ -50,6 +50,8 @@ pub struct Menu { pub size: (usize, usize), /// Wide mode? wide: bool, + /// Scroll by how many lines? + scroll: usize, } /// Represents a line in a Gopher menu. Provides the actual text of @@ -254,6 +256,7 @@ impl Menu { tls, tor: config.read().unwrap().tor, wide: config.read().unwrap().wide, + scroll: config.read().unwrap().scroll, mode: config.read().unwrap().mode, ..parse(url, response) } @@ -287,6 +290,14 @@ impl Menu { self.size.1 } + fn scroll_by(&self) -> usize { + if self.scroll == 0 { + self.rows() - 1 + } else { + self.scroll + } + } + /// Calculated size of left margin. fn indent(&self) -> usize { if self.wide { @@ -318,9 +329,9 @@ impl Menu { /// Where is the given link relative to the screen? fn link_visibility(&self, i: usize) -> Option { let &pos = self.links.get(i)?; - Some(if pos < self.scroll { + Some(if pos < self.offset { LinkPos::Above - } else if pos >= self.scroll + self.rows() - 1 { + } else if pos >= self.offset + self.rows() - 1 { LinkPos::Below } else { LinkPos::Visible @@ -334,10 +345,10 @@ impl Menu { } let &pos = self.links.get(link)?; let x = self.indent() + 1; - let y = if self.scroll > pos { + let y = if self.offset > pos { pos + 1 } else { - pos + 1 - self.scroll + pos + 1 - self.offset }; Some((x as u16, y as u16)) @@ -352,7 +363,7 @@ impl Menu { } else { self.spans.len() }; - let iter = self.lines().skip(self.scroll).take(limit); + let iter = self.lines().skip(self.offset).take(limit); let indent = self.indent(); let left_margin = " ".repeat(indent); @@ -475,7 +486,7 @@ impl Menu { } } - /// Scroll down by SCROLL_LINES, if possible. + /// Scroll down by a page, if possible. fn action_page_down(&mut self) -> Action { // If there are fewer menu items than screen lines, just // select the final link and do nothing else. @@ -489,8 +500,8 @@ impl Menu { // If we've already scrolled too far, select the final link // and do nothing. - if self.scroll >= self.final_scroll() { - self.scroll = self.final_scroll(); + if self.offset >= self.final_offset() { + self.offset = self.final_offset(); if !self.links.is_empty() { self.link = self.links.len() - 1; } @@ -498,11 +509,11 @@ impl Menu { } // Scroll... - self.scroll += SCROLL_LINES; + self.offset += self.scroll_by(); // ...but don't go past the final line. - if self.scroll > self.final_scroll() { - self.scroll = self.final_scroll(); + if self.offset > self.final_offset() { + self.offset = self.final_offset(); } // If the selected link isn't visible... @@ -512,7 +523,7 @@ impl Menu { .links .iter() .skip(self.link + 1) - .find(|&&i| i >= self.scroll) + .find(|&&i| i >= self.offset) { if let Some(next_link_line) = self.line(next_link_pos) { self.link = next_link_line.link; @@ -524,11 +535,11 @@ impl Menu { } fn action_page_up(&mut self) -> Action { - if self.scroll > 0 { - if self.scroll > SCROLL_LINES { - self.scroll -= SCROLL_LINES; + if self.offset > 0 { + if self.offset > self.scroll_by() { + self.offset -= self.scroll_by(); } else { - self.scroll = 0; + self.offset = 0; } if self.link == 0 { return Action::Redraw; @@ -536,7 +547,7 @@ impl Menu { if let Some(dir) = self.link_visibility(self.link) { match dir { LinkPos::Below => { - let scroll = self.scroll; + let scroll = self.offset; if let Some(&pos) = self .links .iter() @@ -563,8 +574,8 @@ impl Menu { fn action_up(&mut self) -> Action { // no links, just scroll up if self.link == 0 { - return if self.scroll > 0 { - self.scroll -= 1; + return if self.offset > 0 { + self.offset -= 1; Action::Redraw } else if !self.links.is_empty() { self.link = self.links.len() - 1; @@ -589,8 +600,8 @@ impl Menu { match dir { LinkPos::Above => { // scroll up by 1 - if self.scroll > 0 { - self.scroll -= 1; + if self.offset > 0 { + self.offset -= 1; } // select it if it's visible now if self.is_visible(new_link) { @@ -600,7 +611,7 @@ impl Menu { LinkPos::Below => { // jump to link.... if let Some(&pos) = self.links.get(new_link) { - self.scroll = pos; + self.offset = pos; self.link = new_link; } } @@ -610,8 +621,8 @@ impl Menu { self.link = new_link; // scroll if we are within 5 lines of the top if let Some(&pos) = self.links.get(self.link) { - if self.scroll > 0 && pos < self.scroll + 5 { - self.scroll -= 1; + if self.offset > 0 && pos < self.offset + 5 { + self.offset -= 1; } else { // otherwise redraw just the cursor return self.reset_cursor(old_link); @@ -625,8 +636,8 @@ impl Menu { } } - /// Final `self.scroll` value. - fn final_scroll(&self) -> usize { + /// Final `self.offset` value. + fn final_offset(&self) -> usize { let padding = (self.rows() as f64 * 0.9) as usize; if self.spans.len() > padding { self.spans.len() - padding @@ -666,13 +677,13 @@ impl Menu { // no links or final link selected already if self.links.is_empty() || new_link >= self.links.len() { // if there are more rows, scroll down - if self.spans.len() >= self.rows() && self.scroll < self.final_scroll() { - self.scroll += 1; + if self.spans.len() >= self.rows() && self.offset < self.final_offset() { + self.offset += 1; return Action::Redraw; } else if !self.links.is_empty() { // wrap around self.link = 0; - self.scroll = 0; + self.offset = 0; return Action::Redraw; } } @@ -692,13 +703,13 @@ impl Menu { LinkPos::Above => { // jump to link.... if let Some(&pos) = self.links.get(new_link) { - self.scroll = pos; + self.offset = pos; self.link = new_link; } } LinkPos::Below => { // scroll down by 1 - self.scroll += 1; + self.offset += 1; // select it if it's visible now if self.is_visible(new_link) { self.link = new_link; @@ -712,9 +723,9 @@ impl Menu { // scroll if we are within 5 lines of the end if self.spans.len() >= self.rows() // dont scroll if content too small - && pos >= self.scroll + self.rows() - 6 + && pos >= self.offset + self.rows() - 6 { - self.scroll += 1; + self.offset += 1; } else { // otherwise try to just re-draw the cursor return self.reset_cursor(old_link); @@ -744,9 +755,9 @@ impl Menu { } } else { if pos > 5 { - self.scroll = pos - 5; + self.offset = pos - 5; } else { - self.scroll = 0; + self.offset = 0; } if !self.input.is_empty() { Action::List(vec![self.redraw_input(), Action::Redraw]) @@ -770,12 +781,12 @@ impl Menu { if !self.is_visible(link) { if let Some(&pos) = self.links.get(link) { if pos > 5 { - self.scroll = pos - 5; + self.offset = pos - 5; } else { - self.scroll = 0; + self.offset = 0; } - if self.scroll > self.final_scroll() { - self.scroll = self.final_scroll(); + if self.offset > self.final_offset() { + self.offset = self.final_offset(); } return Action::Redraw; } @@ -858,12 +869,12 @@ impl Menu { Key::PageUp | Key::Ctrl('-') | Key::Char('-') => self.action_page_up(), Key::PageDown | Key::Ctrl(' ') | Key::Char(' ') => self.action_page_down(), Key::Home => { - self.scroll = 0; + self.offset = 0; self.link = 0; Action::Redraw } Key::End => { - self.scroll = self.final_scroll(); + self.offset = self.final_offset(); if !self.links.is_empty() { self.link = self.links.len() - 1; } @@ -964,12 +975,13 @@ pub fn parse(url: &str, raw: String) -> Menu { input: String::new(), link: 0, mode: Default::default(), - scroll: 0, + offset: 0, searching: false, size: (0, 0), tls: false, tor: false, wide: false, + scroll: 0, } } @@ -1157,7 +1169,7 @@ i Err bitreich.org 70 assert_eq!(menu.link, 7); assert_eq!(menu.link(menu.link).unwrap().link, 7); - assert_eq!(menu.scroll, 0); + assert_eq!(menu.offset, 0); menu.action_page_up(); assert_eq!(menu.link, 0); assert_eq!(menu.link(menu.link).unwrap().link, 0); diff --git a/src/text.rs b/src/text.rs index 8cfd2ab..7852448 100644 --- a/src/text.rs +++ b/src/text.rs @@ -6,7 +6,7 @@ use crate::{ config::SharedConfig as Config, encoding::Encoding, terminal, - ui::{self, Action, Key, View, MAX_COLS, SCROLL_LINES}, + ui::{self, Action, Key, View, MAX_COLS}, }; use std::{borrow::Cow, fmt, str}; @@ -22,7 +22,7 @@ pub struct Text { /// Encoded response encoded_response: String, /// Current scroll offset, in rows - scroll: usize, + offset: usize, /// Number of lines lines: usize, /// Size of longest line @@ -39,6 +39,8 @@ pub struct Text { encoding: Encoding, /// Currently in wide mode? pub wide: bool, + /// How many lines to scroll by. 0 = full screen + scroll: usize, } impl fmt::Display for Text { @@ -83,36 +85,36 @@ impl View for Text { fn respond(&mut self, c: Key) -> Action { match c { Key::Home => { - self.scroll = 0; + self.offset = 0; Action::Redraw } Key::End => { - self.scroll = self.final_scroll(); + self.offset = self.final_scroll(); Action::Redraw } Key::Ctrl('e') | Key::Char('e') => self.toggle_encoding(), Key::Down | Key::Ctrl('n') | Key::Char('n') | Key::Ctrl('j') | Key::Char('j') => { - if self.scroll < self.final_scroll() { - self.scroll += 1; + if self.offset < self.final_scroll() { + self.offset += 1; Action::Redraw } else { Action::None } } Key::Up | Key::Ctrl('p') | Key::Char('p') | Key::Ctrl('k') | Key::Char('k') => { - if self.scroll > 0 { - self.scroll -= 1; + if self.offset > 0 { + self.offset -= 1; Action::Redraw } else { Action::None } } Key::PageUp | Key::Char('-') => { - if self.scroll > 0 { - if self.scroll >= SCROLL_LINES { - self.scroll -= SCROLL_LINES; + if self.offset > 0 { + if self.offset >= self.scroll_by() { + self.offset -= self.scroll_by(); } else { - self.scroll = 0; + self.offset = 0; } Action::Redraw } else { @@ -120,9 +122,9 @@ impl View for Text { } } Key::PageDown | Key::Char(' ') => { - self.scroll += SCROLL_LINES; - if self.scroll > self.final_scroll() { - self.scroll = self.final_scroll(); + self.offset += self.scroll_by(); + if self.offset > self.final_scroll() { + self.offset = self.final_scroll(); } Action::Redraw } @@ -143,7 +145,7 @@ impl View for Text { let iter = wrap_text(&self.encoded_response, wrap) .into_iter() - .skip(self.scroll) + .skip(self.offset) .take(limit); for line in iter { @@ -177,13 +179,14 @@ impl Text { let tor = config.read().unwrap().tor; let encoding = config.read().unwrap().encoding; let wide = config.read().unwrap().wide; + let scroll = config.read().unwrap().scroll; let mut new = Text { config, url: url.into(), encoded_response: String::new(), raw_response: response, - scroll: 0, + offset: 0, lines: 0, longest: 0, size: (0, 0), @@ -192,6 +195,7 @@ impl Text { tor, encoding, wide, + scroll, }; new.encode_response(); new @@ -231,6 +235,15 @@ impl Text { } } + /// How many lines to scroll by when paging up or down. + fn scroll_by(&self) -> usize { + if self.scroll == 0 { + self.size.1 - 1 + } else { + self.scroll + } + } + /// Determine the longest line, considering any line wrapping and /// `MAX_COL`. fn longest_line_with_wrap(&self, wrap: usize) -> usize { diff --git a/src/ui.rs b/src/ui.rs index 27bc681..afe9947 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -45,9 +45,6 @@ pub type Key = termion::event::Key; /// Channel to receive Key events on. pub type KeyReceiver = Arc>>; -/// How many lines to jump by when using page up/down. -pub const SCROLL_LINES: usize = 15; - /// How big the longest line can be, for the purposes of calculating /// margin sizes. We often draw longer lines than this and allow /// wrapping in text views.