Merge pull request #287 from michael-lazar/help_footer

Help footer
This commit is contained in:
Michael Lazar
2016-08-15 21:48:21 -07:00
committed by GitHub
8 changed files with 100 additions and 54 deletions

View File

@@ -16,47 +16,49 @@ Move the cursor using either the arrow keys or *Vim* style movement.
Press `?` to open the help screen. Press `?` to open the help screen.
""" """
HELP = """ HELP = """\
====================================
Reddit Terminal Viewer Reddit Terminal Viewer
https://github.com/michael-lazar/rtv https://github.com/michael-lazar/rtv
==================================== ====================================
[Basic Commands] [Commands]
j/k or ▲/▼ : Move the cursor up/down j : Move the cursor up
m/n or PgUp/PgDn : Jump to the previous/next page k : Move the cursor down
gg/G : Jump to the top/bottom of the page m : Move up one page
1-5 : Toggle post order n : Move down one page
r or F5 : Refresh page content gg : Jump to the first post
u : Log in or switch accounts G : Jump to the last post
/ : Open a prompt to switch subreddits 1 : Sort by hot
? : Show the help screen 2 : Sort by top
q/Q : Quit/Force quit 3 : Sort by rising
4 : Sort by new
[Authenticated Commands] 5 : Sort by controversial
a/z : Upvote/downvote
c : Compose a new post or comment
e : Edit an existing post or comment
d : Delete an existing post or comment
i : Display new messages prompt
s : View a list of subscribed subreddits
S : View a list of subscribed multireddits
w : Save a submission
[Subreddit Commands]
l or ► : Enter the selected submission
o or ENTER : Open the submission link with your web browser
f : Open a prompt to search the current subreddit
p : Return to the front page p : Return to the front page
r : Refresh page
u : Login or logout
/ : Open the subreddit prompt
f : Open the search prompt
? : Show the help screen
q : Quit
Q : Force quit
a : Upvote
z : Downvote
c : Compose a new submission/comment
e : Edit a submission/comment
d : Delete a submission/comment
i : Display new messages
s : Show subscribed subreddits
S : Show subscribed multireddits
w : Save a submission/comment
l : View comments, or open comment in pager
h : Return to subreddit
o : Open the submission or comment url
SPACE : Fold or expand the selected comment tree
b : Display urls with urlview
[Submission Commands] [Prompt]
h or ◄ : Return to the subreddit
l or ► : Open the selected comment in a new window
o or ENTER : Open the comment permalink with your web browser
SPACE : Fold the selected comment, or load additional comments
b : Display URLs with urlview
[Navigating]
The `/` prompt accepts subreddits in the following formats The `/` prompt accepts subreddits in the following formats
- python - python
@@ -72,6 +74,22 @@ https://github.com/michael-lazar/rtv
- /domain/python.org (search by domain) - /domain/python.org (search by domain)
""" """
BANNER = """
[1]hot [2]top [3]rising [4]new [5]controversial
"""
FOOTER_SUBREDDIT = """
[?]Help [q]Quit [l]Comments [/]Prompt [u]Login [o]Open [c]Post [a/z]Vote
"""
FOOTER_SUBMISSION = """
[?]Help [q]Quit [h]Return [space]Fold/Expand [o]Open [c]Comment [a/z]Vote
"""
FOOTER_SUBSCRIPTION = """
[?]Help [q]Quit [h]Return [l]Select
"""
COMMENT_FILE = """ COMMENT_FILE = """
# Please enter a comment. Lines starting with '#' will be ignored, # Please enter a comment. Lines starting with '#' will be ignored,
# and an empty message aborts the comment. # and an empty message aborts the comment.

View File

@@ -34,6 +34,8 @@ class PageController(Controller):
class Page(object): class Page(object):
FOOTER = None
def __init__(self, reddit, term, config, oauth): def __init__(self, reddit, term, config, oauth):
self.reddit = reddit self.reddit = reddit
@@ -299,6 +301,7 @@ class Page(object):
self._draw_header() self._draw_header()
self._draw_banner() self._draw_banner()
self._draw_content() self._draw_content()
self._draw_footer()
self._add_cursor() self._add_cursor()
self.term.stdscr.touchwin() self.term.stdscr.touchwin()
self.term.stdscr.refresh() self.term.stdscr.refresh()
@@ -306,6 +309,7 @@ class Page(object):
def _draw_header(self): def _draw_header(self):
n_rows, n_cols = self.term.stdscr.getmaxyx() n_rows, n_cols = self.term.stdscr.getmaxyx()
# Note: 2 argument form of derwin breaks PDcurses on Windows 7! # Note: 2 argument form of derwin breaks PDcurses on Windows 7!
window = self.term.stdscr.derwin(1, n_cols, self._row, 0) window = self.term.stdscr.derwin(1, n_cols, self._row, 0)
window.erase() window.erase()
@@ -351,7 +355,7 @@ class Page(object):
ch, attr = str(' '), curses.A_BOLD | Color.YELLOW ch, attr = str(' '), curses.A_BOLD | Color.YELLOW
window.bkgd(ch, attr) window.bkgd(ch, attr)
items = ['[1]hot', '[2]top', '[3]rising', '[4]new', '[5]controversial'] items = docs.BANNER.strip().split(' ')
distance = (n_cols - sum(len(t) for t in items) - 1) / (len(items) - 1) distance = (n_cols - sum(len(t) for t in items) - 1) / (len(items) - 1)
spacing = max(1, int(distance)) * ' ' spacing = max(1, int(distance)) * ' '
text = spacing.join(items) text = spacing.join(items)
@@ -370,7 +374,7 @@ class Page(object):
n_rows, n_cols = self.term.stdscr.getmaxyx() n_rows, n_cols = self.term.stdscr.getmaxyx()
window = self.term.stdscr.derwin( window = self.term.stdscr.derwin(
n_rows - self._row, n_cols, self._row, 0) n_rows - self._row - 1, n_cols, self._row, 0)
window.erase() window.erase()
win_n_rows, win_n_cols = window.getmaxyx() win_n_rows, win_n_cols = window.getmaxyx()
@@ -383,7 +387,7 @@ class Page(object):
# and draw upwards. # and draw upwards.
cancel_inverted = True cancel_inverted = True
current_row = (win_n_rows - 1) if inverted else 0 current_row = (win_n_rows - 1) if inverted else 0
available_rows = (win_n_rows - 1) if inverted else win_n_rows available_rows = win_n_rows
top_item_height = None if inverted else self.nav.top_item_height top_item_height = None if inverted else self.nav.top_item_height
for data in self.content.iterate(page_index, step, win_n_cols - 2): for data in self.content.iterate(page_index, step, win_n_cols - 2):
subwin_n_rows = min(available_rows, data['n_rows']) subwin_n_rows = min(available_rows, data['n_rows'])
@@ -396,7 +400,7 @@ class Page(object):
subwin_inverted = True subwin_inverted = True
top_item_height = None top_item_height = None
subwin_n_cols = win_n_cols - data['offset'] subwin_n_cols = win_n_cols - data['offset']
start = current_row - subwin_n_rows if inverted else current_row start = current_row - subwin_n_rows + 1 if inverted else current_row
subwindow = window.derwin( subwindow = window.derwin(
subwin_n_rows, subwin_n_cols, start, data['offset']) subwin_n_rows, subwin_n_cols, start, data['offset'])
attr = self._draw_item(subwindow, data, subwin_inverted) attr = self._draw_item(subwindow, data, subwin_inverted)
@@ -420,9 +424,21 @@ class Page(object):
# if the content will fill up the page, given that it is dependent # if the content will fill up the page, given that it is dependent
# on the size of the terminal. # on the size of the terminal.
self.nav.flip((len(self._subwindows) - 1)) self.nav.flip((len(self._subwindows) - 1))
self._draw_content() return self._draw_content()
self._row = n_rows self._row += win_n_rows
def _draw_footer(self):
n_rows, n_cols = self.term.stdscr.getmaxyx()
window = self.term.stdscr.derwin(1, n_cols, self._row, 0)
window.erase()
ch, attr = str(' '), curses.A_REVERSE | curses.A_BOLD | Color.CYAN
window.bkgd(ch, attr)
text = self.FOOTER.strip()
self.term.add_line(window, text, 0, 0)
self._row += 1
def _add_cursor(self): def _add_cursor(self):
self._edit_cursor(curses.A_REVERSE) self._edit_cursor(curses.A_REVERSE)

View File

@@ -17,6 +17,8 @@ class SubmissionController(PageController):
class SubmissionPage(Page): class SubmissionPage(Page):
FOOTER = docs.FOOTER_SUBMISSION
def __init__(self, reddit, term, config, oauth, url=None, submission=None): def __init__(self, reddit, term, config, oauth, url=None, submission=None):
super(SubmissionPage, self).__init__(reddit, term, config, oauth) super(SubmissionPage, self).__init__(reddit, term, config, oauth)

View File

@@ -19,6 +19,8 @@ class SubredditController(PageController):
class SubredditPage(Page): class SubredditPage(Page):
FOOTER = docs.FOOTER_SUBREDDIT
def __init__(self, reddit, term, config, oauth, name): def __init__(self, reddit, term, config, oauth, name):
""" """
Params: Params:
@@ -265,7 +267,7 @@ class SubredditPage(Page):
row = n_title + offset + 2 row = n_title + offset + 2
if row in valid_rows: if row in valid_rows:
text = '{author}'.format(**data) text = '{author}'.format(**data)
self.term.add_line(win, text, row, 1, curses.A_BOLD) self.term.add_line(win, text, row, 1, Color.GREEN)
text = ' /r/{subreddit}'.format(**data) text = ' /r/{subreddit}'.format(**data)
self.term.add_line(win, text, attr=Color.YELLOW) self.term.add_line(win, text, attr=Color.YELLOW)
if data['flair']: if data['flair']:

View File

@@ -3,6 +3,7 @@ from __future__ import unicode_literals
import curses import curses
from . import docs
from .page import Page, PageController from .page import Page, PageController
from .content import SubscriptionContent, SubredditContent from .content import SubscriptionContent, SubredditContent
from .objects import Color, Navigator, Command from .objects import Color, Navigator, Command
@@ -14,6 +15,8 @@ class SubscriptionController(PageController):
class SubscriptionPage(Page): class SubscriptionPage(Page):
FOOTER = docs.FOOTER_SUBSCRIPTION
def __init__(self, reddit, term, config, oauth, content_type='subreddit'): def __init__(self, reddit, term, config, oauth, content_type='subreddit'):
super(SubscriptionPage, self).__init__(reddit, term, config, oauth) super(SubscriptionPage, self).__init__(reddit, term, config, oauth)

View File

@@ -664,18 +664,20 @@ class Terminal(object):
""" """
n_rows, n_cols = self.stdscr.getmaxyx() n_rows, n_cols = self.stdscr.getmaxyx()
attr = curses.A_BOLD | Color.CYAN ch, attr = str(' '), curses.A_BOLD | curses.A_REVERSE | Color.CYAN
prompt = self.clean(prompt, n_cols-1) prompt = self.clean(prompt, n_cols-1)
# Create a new window to draw the text at the bottom of the screen, # Create a new window to draw the text at the bottom of the screen,
# so we can erase it when we're done. # so we can erase it when we're done.
prompt_win = curses.newwin(1, len(prompt)+1, n_rows-1, 0) prompt_win = curses.newwin(1, len(prompt)+1, n_rows-1, 0)
self.add_line(prompt_win, prompt, attr=attr) prompt_win.bkgd(ch, attr)
self.add_line(prompt_win, prompt)
prompt_win.refresh() prompt_win.refresh()
# Create a separate window for text input # Create a separate window for text input
input_win = curses.newwin(1, n_cols-len(prompt), n_rows-1, len(prompt)) input_win = curses.newwin(1, n_cols-len(prompt), n_rows-1, len(prompt))
input_win.attrset(attr) input_win.bkgd(ch, attr)
input_win.refresh()
if key: if key:
curses.curs_set(1) curses.curs_set(1)

View File

@@ -51,6 +51,11 @@ def test_submission_page_construct(reddit, terminal, config, oauth):
'[5]controversial').encode('utf-8') '[5]controversial').encode('utf-8')
window.addstr.assert_any_call(0, 0, menu) window.addstr.assert_any_call(0, 0, menu)
# Footer
text = ('[?]Help [q]Quit [h]Return [space]Fold/Expand [o]Open [c]Comment '
'[a/z]Vote'.encode('utf-8'))
window.addstr.assert_any_call(0, 0, text)
# Submission # Submission
submission_data = page.content.get(-1) submission_data = page.content.get(-1)
text = submission_data['title'].encode('utf-8') text = submission_data['title'].encode('utf-8')
@@ -181,7 +186,7 @@ def test_submission_comment_not_enough_space(submission_page, terminal):
text = '(Not enough space to display)'.encode('ascii') text = '(Not enough space to display)'.encode('ascii')
window = terminal.stdscr.subwin window = terminal.stdscr.subwin
window.subwin.addstr.assert_any_call(7, 1, text) window.subwin.addstr.assert_any_call(6, 1, text)
def test_submission_vote(submission_page, refresh_token): def test_submission_vote(submission_page, refresh_token):

View File

@@ -236,8 +236,7 @@ def test_prompt_input(terminal, stdscr, use_ascii):
window.getch.side_effect = [ord('h'), ord('i'), terminal.RETURN] window.getch.side_effect = [ord('h'), ord('i'), terminal.RETURN]
assert isinstance(terminal.prompt_input('hi'), six.text_type) assert isinstance(terminal.prompt_input('hi'), six.text_type)
attr = Color.CYAN | curses.A_BOLD stdscr.subwin.addstr.assert_called_with(0, 0, 'hi'.encode('ascii'))
stdscr.subwin.addstr.assert_called_with(0, 0, 'hi'.encode('ascii'), attr)
assert window.nlines == 1 assert window.nlines == 1
assert window.ncols == 78 assert window.ncols == 78
@@ -254,27 +253,26 @@ def test_prompt_input(terminal, stdscr, use_ascii):
def test_prompt_y_or_n(terminal, stdscr): def test_prompt_y_or_n(terminal, stdscr):
stdscr.getch.side_effect = [ord('y'), ord('N'), terminal.ESCAPE, ord('a')] stdscr.getch.side_effect = [ord('y'), ord('N'), terminal.ESCAPE, ord('a')]
attr = Color.CYAN | curses.A_BOLD
text = 'hi'.encode('ascii') text = 'hi'.encode('ascii')
# Press 'y' # Press 'y'
assert terminal.prompt_y_or_n('hi') assert terminal.prompt_y_or_n('hi')
stdscr.subwin.addstr.assert_called_with(0, 0, text, attr) stdscr.subwin.addstr.assert_called_with(0, 0, text)
assert not curses.flash.called assert not curses.flash.called
# Press 'N' # Press 'N'
assert not terminal.prompt_y_or_n('hi') assert not terminal.prompt_y_or_n('hi')
stdscr.subwin.addstr.assert_called_with(0, 0, text, attr) stdscr.subwin.addstr.assert_called_with(0, 0, text)
assert not curses.flash.called assert not curses.flash.called
# Press Esc # Press Esc
assert not terminal.prompt_y_or_n('hi') assert not terminal.prompt_y_or_n('hi')
stdscr.subwin.addstr.assert_called_with(0, 0, text, attr) stdscr.subwin.addstr.assert_called_with(0, 0, text)
assert not curses.flash.called assert not curses.flash.called
# Press an invalid key # Press an invalid key
assert not terminal.prompt_y_or_n('hi') assert not terminal.prompt_y_or_n('hi')
stdscr.subwin.addstr.assert_called_with(0, 0, text, attr) stdscr.subwin.addstr.assert_called_with(0, 0, text)
assert curses.flash.called assert curses.flash.called