add `scroll` config option and default to entire screen

pull/33/head
chris west 2 years ago
parent 4029fca177
commit 6861848217

@ -2,6 +2,8 @@
This release adds a few new config options, for your convenience: 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 - `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 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. to `true` if hosting, say, a Gopher-powered music server.

@ -235,6 +235,9 @@ encoding utf8
# Wrap text at N columns. 0 = off (--wrap) # Wrap text at N columns. 0 = off (--wrap)
wrap 0 wrap 0
# How many lines to page up/down by? 0 = full screen
scroll 0
``` ```
# MEDIA PLAYER SUPPORT # MEDIA PLAYER SUPPORT

@ -55,6 +55,9 @@ encoding utf8
# Wrap text at N columns. 0 = off (--wrap) # Wrap text at N columns. 0 = off (--wrap)
wrap 0 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 /// Not all the config options are available in the phetch.conf. We
@ -82,6 +85,8 @@ pub struct Config {
pub mode: ui::Mode, pub mode: ui::Mode,
/// Column to wrap lines. 0 = off /// Column to wrap lines. 0 = off
pub wrap: usize, pub wrap: usize,
/// Scroll by how many lines? 0 = full screen
pub scroll: usize,
} }
impl Default for Config { impl Default for Config {
@ -97,6 +102,7 @@ impl Default for Config {
encoding: Encoding::default(), encoding: Encoding::default(),
mode: ui::Mode::default(), mode: ui::Mode::default(),
wrap: 0, wrap: 0,
scroll: 0,
} }
} }
} }
@ -173,15 +179,23 @@ fn parse(text: &str) -> Result<Config> {
)); ));
} }
} }
"scroll" => {
if let Ok(num) = val.parse() {
cfg.scroll = num;
} else {
return Err(error!(
"`scroll` expects a number value on line {}: {}",
linenum, val
));
}
}
"media" => { "media" => {
cfg.media = match val.to_lowercase().as_ref() { cfg.media = match val.to_lowercase().as_ref() {
"false" | "none" => None, "false" | "none" => None,
_ => Some(val.into()), _ => Some(val.into()),
} }
} }
"autoplay" => { "autoplay" => cfg.autoplay = to_bool(val)?,
cfg.autoplay = to_bool(val)?
}
"encoding" => { "encoding" => {
cfg.encoding = Encoding::from_str(val) cfg.encoding = Encoding::from_str(val)
.map_err(|e| error!("{} on line {}: {:?}", e, linenum, line))?; .map_err(|e| error!("{} on line {}: {:?}", e, linenum, line))?;

@ -10,7 +10,7 @@ use crate::{
config::SharedConfig as Config, config::SharedConfig as Config,
gopher::{self, Type}, gopher::{self, Type},
terminal, terminal,
ui::{self, Action, Key, View, MAX_COLS, SCROLL_LINES}, ui::{self, Action, Key, View, MAX_COLS},
}; };
use std::fmt; use std::fmt;
@ -38,8 +38,8 @@ pub struct Menu {
pub input: String, pub input: String,
/// UI mode. Interactive (Run), Printing, Raw mode... /// UI mode. Interactive (Run), Printing, Raw mode...
pub mode: ui::Mode, pub mode: ui::Mode,
/// Scrolling offset, in rows. /// Scrolling offset, in rows. 0 = full screen
pub scroll: usize, pub offset: usize,
/// Incremental search mode? /// Incremental search mode?
pub searching: bool, pub searching: bool,
/// Was this menu retrieved via TLS? /// Was this menu retrieved via TLS?
@ -50,6 +50,8 @@ pub struct Menu {
pub size: (usize, usize), pub size: (usize, usize),
/// Wide mode? /// Wide mode?
wide: bool, wide: bool,
/// Scroll by how many lines?
scroll: usize,
} }
/// Represents a line in a Gopher menu. Provides the actual text of /// Represents a line in a Gopher menu. Provides the actual text of
@ -254,6 +256,7 @@ impl Menu {
tls, tls,
tor: config.read().unwrap().tor, tor: config.read().unwrap().tor,
wide: config.read().unwrap().wide, wide: config.read().unwrap().wide,
scroll: config.read().unwrap().scroll,
mode: config.read().unwrap().mode, mode: config.read().unwrap().mode,
..parse(url, response) ..parse(url, response)
} }
@ -287,6 +290,14 @@ impl Menu {
self.size.1 self.size.1
} }
fn scroll_by(&self) -> usize {
if self.scroll == 0 {
self.rows() - 1
} else {
self.scroll
}
}
/// Calculated size of left margin. /// Calculated size of left margin.
fn indent(&self) -> usize { fn indent(&self) -> usize {
if self.wide { if self.wide {
@ -318,9 +329,9 @@ impl Menu {
/// Where is the given link relative to the screen? /// Where is the given link relative to the screen?
fn link_visibility(&self, i: usize) -> Option<LinkPos> { fn link_visibility(&self, i: usize) -> Option<LinkPos> {
let &pos = self.links.get(i)?; let &pos = self.links.get(i)?;
Some(if pos < self.scroll { Some(if pos < self.offset {
LinkPos::Above LinkPos::Above
} else if pos >= self.scroll + self.rows() - 1 { } else if pos >= self.offset + self.rows() - 1 {
LinkPos::Below LinkPos::Below
} else { } else {
LinkPos::Visible LinkPos::Visible
@ -334,10 +345,10 @@ impl Menu {
} }
let &pos = self.links.get(link)?; let &pos = self.links.get(link)?;
let x = self.indent() + 1; let x = self.indent() + 1;
let y = if self.scroll > pos { let y = if self.offset > pos {
pos + 1 pos + 1
} else { } else {
pos + 1 - self.scroll pos + 1 - self.offset
}; };
Some((x as u16, y as u16)) Some((x as u16, y as u16))
@ -352,7 +363,7 @@ impl Menu {
} else { } else {
self.spans.len() 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 indent = self.indent();
let left_margin = " ".repeat(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 { fn action_page_down(&mut self) -> Action {
// If there are fewer menu items than screen lines, just // If there are fewer menu items than screen lines, just
// select the final link and do nothing else. // 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 // If we've already scrolled too far, select the final link
// and do nothing. // and do nothing.
if self.scroll >= self.final_scroll() { if self.offset >= self.final_offset() {
self.scroll = self.final_scroll(); self.offset = self.final_offset();
if !self.links.is_empty() { if !self.links.is_empty() {
self.link = self.links.len() - 1; self.link = self.links.len() - 1;
} }
@ -498,11 +509,11 @@ impl Menu {
} }
// Scroll... // Scroll...
self.scroll += SCROLL_LINES; self.offset += self.scroll_by();
// ...but don't go past the final line. // ...but don't go past the final line.
if self.scroll > self.final_scroll() { if self.offset > self.final_offset() {
self.scroll = self.final_scroll(); self.offset = self.final_offset();
} }
// If the selected link isn't visible... // If the selected link isn't visible...
@ -512,7 +523,7 @@ impl Menu {
.links .links
.iter() .iter()
.skip(self.link + 1) .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) { if let Some(next_link_line) = self.line(next_link_pos) {
self.link = next_link_line.link; self.link = next_link_line.link;
@ -524,11 +535,11 @@ impl Menu {
} }
fn action_page_up(&mut self) -> Action { fn action_page_up(&mut self) -> Action {
if self.scroll > 0 { if self.offset > 0 {
if self.scroll > SCROLL_LINES { if self.offset > self.scroll_by() {
self.scroll -= SCROLL_LINES; self.offset -= self.scroll_by();
} else { } else {
self.scroll = 0; self.offset = 0;
} }
if self.link == 0 { if self.link == 0 {
return Action::Redraw; return Action::Redraw;
@ -536,7 +547,7 @@ impl Menu {
if let Some(dir) = self.link_visibility(self.link) { if let Some(dir) = self.link_visibility(self.link) {
match dir { match dir {
LinkPos::Below => { LinkPos::Below => {
let scroll = self.scroll; let scroll = self.offset;
if let Some(&pos) = self if let Some(&pos) = self
.links .links
.iter() .iter()
@ -563,8 +574,8 @@ impl Menu {
fn action_up(&mut self) -> Action { fn action_up(&mut self) -> Action {
// no links, just scroll up // no links, just scroll up
if self.link == 0 { if self.link == 0 {
return if self.scroll > 0 { return if self.offset > 0 {
self.scroll -= 1; self.offset -= 1;
Action::Redraw Action::Redraw
} else if !self.links.is_empty() { } else if !self.links.is_empty() {
self.link = self.links.len() - 1; self.link = self.links.len() - 1;
@ -589,8 +600,8 @@ impl Menu {
match dir { match dir {
LinkPos::Above => { LinkPos::Above => {
// scroll up by 1 // scroll up by 1
if self.scroll > 0 { if self.offset > 0 {
self.scroll -= 1; self.offset -= 1;
} }
// select it if it's visible now // select it if it's visible now
if self.is_visible(new_link) { if self.is_visible(new_link) {
@ -600,7 +611,7 @@ impl Menu {
LinkPos::Below => { LinkPos::Below => {
// jump to link.... // jump to link....
if let Some(&pos) = self.links.get(new_link) { if let Some(&pos) = self.links.get(new_link) {
self.scroll = pos; self.offset = pos;
self.link = new_link; self.link = new_link;
} }
} }
@ -610,8 +621,8 @@ impl Menu {
self.link = new_link; self.link = new_link;
// scroll if we are within 5 lines of the top // scroll if we are within 5 lines of the top
if let Some(&pos) = self.links.get(self.link) { if let Some(&pos) = self.links.get(self.link) {
if self.scroll > 0 && pos < self.scroll + 5 { if self.offset > 0 && pos < self.offset + 5 {
self.scroll -= 1; self.offset -= 1;
} else { } else {
// otherwise redraw just the cursor // otherwise redraw just the cursor
return self.reset_cursor(old_link); return self.reset_cursor(old_link);
@ -625,8 +636,8 @@ impl Menu {
} }
} }
/// Final `self.scroll` value. /// Final `self.offset` value.
fn final_scroll(&self) -> usize { fn final_offset(&self) -> usize {
let padding = (self.rows() as f64 * 0.9) as usize; let padding = (self.rows() as f64 * 0.9) as usize;
if self.spans.len() > padding { if self.spans.len() > padding {
self.spans.len() - padding self.spans.len() - padding
@ -666,13 +677,13 @@ impl Menu {
// no links or final link selected already // no links or final link selected already
if self.links.is_empty() || new_link >= self.links.len() { if self.links.is_empty() || new_link >= self.links.len() {
// if there are more rows, scroll down // if there are more rows, scroll down
if self.spans.len() >= self.rows() && self.scroll < self.final_scroll() { if self.spans.len() >= self.rows() && self.offset < self.final_offset() {
self.scroll += 1; self.offset += 1;
return Action::Redraw; return Action::Redraw;
} else if !self.links.is_empty() { } else if !self.links.is_empty() {
// wrap around // wrap around
self.link = 0; self.link = 0;
self.scroll = 0; self.offset = 0;
return Action::Redraw; return Action::Redraw;
} }
} }
@ -692,13 +703,13 @@ impl Menu {
LinkPos::Above => { LinkPos::Above => {
// jump to link.... // jump to link....
if let Some(&pos) = self.links.get(new_link) { if let Some(&pos) = self.links.get(new_link) {
self.scroll = pos; self.offset = pos;
self.link = new_link; self.link = new_link;
} }
} }
LinkPos::Below => { LinkPos::Below => {
// scroll down by 1 // scroll down by 1
self.scroll += 1; self.offset += 1;
// select it if it's visible now // select it if it's visible now
if self.is_visible(new_link) { if self.is_visible(new_link) {
self.link = new_link; self.link = new_link;
@ -712,9 +723,9 @@ impl Menu {
// scroll if we are within 5 lines of the end // scroll if we are within 5 lines of the end
if self.spans.len() >= self.rows() // dont scroll if content too small 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 { } else {
// otherwise try to just re-draw the cursor // otherwise try to just re-draw the cursor
return self.reset_cursor(old_link); return self.reset_cursor(old_link);
@ -744,9 +755,9 @@ impl Menu {
} }
} else { } else {
if pos > 5 { if pos > 5 {
self.scroll = pos - 5; self.offset = pos - 5;
} else { } else {
self.scroll = 0; self.offset = 0;
} }
if !self.input.is_empty() { if !self.input.is_empty() {
Action::List(vec![self.redraw_input(), Action::Redraw]) Action::List(vec![self.redraw_input(), Action::Redraw])
@ -770,12 +781,12 @@ impl Menu {
if !self.is_visible(link) { if !self.is_visible(link) {
if let Some(&pos) = self.links.get(link) { if let Some(&pos) = self.links.get(link) {
if pos > 5 { if pos > 5 {
self.scroll = pos - 5; self.offset = pos - 5;
} else { } else {
self.scroll = 0; self.offset = 0;
} }
if self.scroll > self.final_scroll() { if self.offset > self.final_offset() {
self.scroll = self.final_scroll(); self.offset = self.final_offset();
} }
return Action::Redraw; return Action::Redraw;
} }
@ -858,12 +869,12 @@ impl Menu {
Key::PageUp | Key::Ctrl('-') | Key::Char('-') => self.action_page_up(), Key::PageUp | Key::Ctrl('-') | Key::Char('-') => self.action_page_up(),
Key::PageDown | Key::Ctrl(' ') | Key::Char(' ') => self.action_page_down(), Key::PageDown | Key::Ctrl(' ') | Key::Char(' ') => self.action_page_down(),
Key::Home => { Key::Home => {
self.scroll = 0; self.offset = 0;
self.link = 0; self.link = 0;
Action::Redraw Action::Redraw
} }
Key::End => { Key::End => {
self.scroll = self.final_scroll(); self.offset = self.final_offset();
if !self.links.is_empty() { if !self.links.is_empty() {
self.link = self.links.len() - 1; self.link = self.links.len() - 1;
} }
@ -964,12 +975,13 @@ pub fn parse(url: &str, raw: String) -> Menu {
input: String::new(), input: String::new(),
link: 0, link: 0,
mode: Default::default(), mode: Default::default(),
scroll: 0, offset: 0,
searching: false, searching: false,
size: (0, 0), size: (0, 0),
tls: false, tls: false,
tor: false, tor: false,
wide: false, wide: false,
scroll: 0,
} }
} }
@ -1157,7 +1169,7 @@ i Err bitreich.org 70
assert_eq!(menu.link, 7); assert_eq!(menu.link, 7);
assert_eq!(menu.link(menu.link).unwrap().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(); menu.action_page_up();
assert_eq!(menu.link, 0); assert_eq!(menu.link, 0);
assert_eq!(menu.link(menu.link).unwrap().link, 0); assert_eq!(menu.link(menu.link).unwrap().link, 0);

@ -6,7 +6,7 @@ use crate::{
config::SharedConfig as Config, config::SharedConfig as Config,
encoding::Encoding, encoding::Encoding,
terminal, terminal,
ui::{self, Action, Key, View, MAX_COLS, SCROLL_LINES}, ui::{self, Action, Key, View, MAX_COLS},
}; };
use std::{borrow::Cow, fmt, str}; use std::{borrow::Cow, fmt, str};
@ -22,7 +22,7 @@ pub struct Text {
/// Encoded response /// Encoded response
encoded_response: String, encoded_response: String,
/// Current scroll offset, in rows /// Current scroll offset, in rows
scroll: usize, offset: usize,
/// Number of lines /// Number of lines
lines: usize, lines: usize,
/// Size of longest line /// Size of longest line
@ -39,6 +39,8 @@ pub struct Text {
encoding: Encoding, encoding: Encoding,
/// Currently in wide mode? /// Currently in wide mode?
pub wide: bool, pub wide: bool,
/// How many lines to scroll by. 0 = full screen
scroll: usize,
} }
impl fmt::Display for Text { impl fmt::Display for Text {
@ -83,36 +85,36 @@ impl View for Text {
fn respond(&mut self, c: Key) -> Action { fn respond(&mut self, c: Key) -> Action {
match c { match c {
Key::Home => { Key::Home => {
self.scroll = 0; self.offset = 0;
Action::Redraw Action::Redraw
} }
Key::End => { Key::End => {
self.scroll = self.final_scroll(); self.offset = self.final_scroll();
Action::Redraw Action::Redraw
} }
Key::Ctrl('e') | Key::Char('e') => self.toggle_encoding(), Key::Ctrl('e') | Key::Char('e') => self.toggle_encoding(),
Key::Down | Key::Ctrl('n') | Key::Char('n') | Key::Ctrl('j') | Key::Char('j') => { Key::Down | Key::Ctrl('n') | Key::Char('n') | Key::Ctrl('j') | Key::Char('j') => {
if self.scroll < self.final_scroll() { if self.offset < self.final_scroll() {
self.scroll += 1; self.offset += 1;
Action::Redraw Action::Redraw
} else { } else {
Action::None Action::None
} }
} }
Key::Up | Key::Ctrl('p') | Key::Char('p') | Key::Ctrl('k') | Key::Char('k') => { Key::Up | Key::Ctrl('p') | Key::Char('p') | Key::Ctrl('k') | Key::Char('k') => {
if self.scroll > 0 { if self.offset > 0 {
self.scroll -= 1; self.offset -= 1;
Action::Redraw Action::Redraw
} else { } else {
Action::None Action::None
} }
} }
Key::PageUp | Key::Char('-') => { Key::PageUp | Key::Char('-') => {
if self.scroll > 0 { if self.offset > 0 {
if self.scroll >= SCROLL_LINES { if self.offset >= self.scroll_by() {
self.scroll -= SCROLL_LINES; self.offset -= self.scroll_by();
} else { } else {
self.scroll = 0; self.offset = 0;
} }
Action::Redraw Action::Redraw
} else { } else {
@ -120,9 +122,9 @@ impl View for Text {
} }
} }
Key::PageDown | Key::Char(' ') => { Key::PageDown | Key::Char(' ') => {
self.scroll += SCROLL_LINES; self.offset += self.scroll_by();
if self.scroll > self.final_scroll() { if self.offset > self.final_scroll() {
self.scroll = self.final_scroll(); self.offset = self.final_scroll();
} }
Action::Redraw Action::Redraw
} }
@ -143,7 +145,7 @@ impl View for Text {
let iter = wrap_text(&self.encoded_response, wrap) let iter = wrap_text(&self.encoded_response, wrap)
.into_iter() .into_iter()
.skip(self.scroll) .skip(self.offset)
.take(limit); .take(limit);
for line in iter { for line in iter {
@ -177,13 +179,14 @@ impl Text {
let tor = config.read().unwrap().tor; let tor = config.read().unwrap().tor;
let encoding = config.read().unwrap().encoding; let encoding = config.read().unwrap().encoding;
let wide = config.read().unwrap().wide; let wide = config.read().unwrap().wide;
let scroll = config.read().unwrap().scroll;
let mut new = Text { let mut new = Text {
config, config,
url: url.into(), url: url.into(),
encoded_response: String::new(), encoded_response: String::new(),
raw_response: response, raw_response: response,
scroll: 0, offset: 0,
lines: 0, lines: 0,
longest: 0, longest: 0,
size: (0, 0), size: (0, 0),
@ -192,6 +195,7 @@ impl Text {
tor, tor,
encoding, encoding,
wide, wide,
scroll,
}; };
new.encode_response(); new.encode_response();
new 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 /// Determine the longest line, considering any line wrapping and
/// `MAX_COL`. /// `MAX_COL`.
fn longest_line_with_wrap(&self, wrap: usize) -> usize { fn longest_line_with_wrap(&self, wrap: usize) -> usize {

@ -45,9 +45,6 @@ pub type Key = termion::event::Key;
/// Channel to receive Key events on. /// Channel to receive Key events on.
pub type KeyReceiver = Arc<Mutex<Receiver<Key>>>; pub type KeyReceiver = Arc<Mutex<Receiver<Key>>>;
/// 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 /// How big the longest line can be, for the purposes of calculating
/// margin sizes. We often draw longer lines than this and allow /// margin sizes. We often draw longer lines than this and allow
/// wrapping in text views. /// wrapping in text views.

Loading…
Cancel
Save