Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Set `HN_REDUCED_MOTION=1` to disable visual animations; ANSI and monochrome pale

## Read a story

Use `j/k` or the arrow keys to select a story. Press `1`, `2`, or `3` for Top, New, or Best; `4` opens Saved. Use `n/p` to change pages — or keep holding `j` past the last story to continue on the next page's first story, and `k` at the top to go back a page.
Use `j/k` or the arrow keys to select a story. Press `1`, `2`, or `3` for Top, New, or Best; `4` opens Saved. Use `n/p` to change pages — or press `j` past the last story to continue on the next page's first story, and `k` at the top to go back a page (further `j/k` wait while the page loads).

Press `o` or Enter to open the discussion and its article. Press `c` to read the discussion without opening a new article pane. If a companion pane is already open, it follows the selected story. `O` always opens the story in your default browser.

Expand Down
17 changes: 13 additions & 4 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,18 @@ impl App {

/// Move to next story. At the last story of the page, continue on the next
/// page instead (the first story once it loads). Returns true when the
/// page advanced and the caller should request the new page.
/// page advanced and the caller should request the new page. While a page
/// or feed switch is still in flight, j/k are ignored so repeated presses
/// cannot stack multiple page advances.
pub fn next_story(&mut self) -> bool {
if self.stories.is_empty() {
if self.stories.is_empty() || self.showing_stale_stories() {
return false;
}
if self.selected_index < self.stories.len() - 1 {
self.move_selection(self.selected_index + 1);
false
} else if self.is_loading() {
false
} else {
let page = self.current_page;
self.next_page();
Expand All @@ -225,12 +229,17 @@ impl App {
}

/// Move to previous story. At the first story of the page, go back to the
/// previous page (restoring the position left there). Returns true when
/// the page changed and the caller should request it.
/// previous page. Returns true when the page changed and the caller should
/// request it. Blocked while a page/feed switch is in flight, like next_story.
pub fn prev_story(&mut self) -> bool {
if self.showing_stale_stories() {
return false;
}
if self.selected_index > 0 {
self.move_selection(self.selected_index - 1);
false
} else if self.is_loading() {
false
} else {
let page = self.current_page;
self.prev_page();
Expand Down
29 changes: 29 additions & 0 deletions src/ui/ui_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -819,3 +819,32 @@ fn j_mid_list_returns_false_and_keeps_selection_on_page() {
assert_eq!(app.selected_index, 1);
assert_eq!(app.current_page, 1);
}

#[test]
fn jk_do_not_stack_while_a_page_switch_is_in_flight() {
let mut app = feed();
app.selected_index = app.stories.len() - 1;
assert!(app.next_story(), "first j advances to page 2");
assert_eq!(app.current_page, 2);
// Page 2 has not loaded: the displayed list is still page 1 (stale), so
// further j/k must be ignored instead of stacking another page advance.
assert!(app.showing_stale_stories());
assert!(!app.next_story(), "j while fetching must not advance to page 3");
assert_eq!(app.current_page, 2);
assert!(!app.prev_story(), "k while a switch is in flight must not move");
assert_eq!(app.current_page, 2);
assert_eq!(app.selected_index, app.stories.len() - 1);
}

#[test]
fn page_advance_blocked_during_same_page_refresh_but_selection_still_moves() {
let mut app = feed();
app.selected_index = app.stories.len() - 1;
app.set_story_loading(true);
assert!(!app.next_story(), "j at the end during a refresh must not advance");
assert_eq!(app.current_page, 1);
let mut app = feed();
app.set_story_loading(true);
assert!(!app.next_story(), "mid-list j still works during a refresh");
assert_eq!(app.selected_index, 1);
}
Loading