diff --git a/README.rst b/README.rst index 85f21f4..c6e5a5f 100644 --- a/README.rst +++ b/README.rst @@ -70,6 +70,7 @@ In subreddit mode you can browse through the top submissions on either the front :``►`` or ``l``: View comments for the selected submission :``/``: Open a prompt to switch subreddits +:``f``: Open a prompt to search the current subreddit The ``/`` prompt accepts subreddits in the following formats diff --git a/rtv/content.py b/rtv/content.py index 302ebea..b1ef638 100644 --- a/rtv/content.py +++ b/rtv/content.py @@ -249,7 +249,7 @@ class SubredditContent(BaseContent): self._submission_data = [] @classmethod - def from_name(cls, reddit, name, loader, order='hot'): + def from_name(cls, reddit, name, loader, order='hot', search=None): if name is None: name = 'front' @@ -266,9 +266,11 @@ class SubredditContent(BaseContent): display_name = '/r/{}'.format(name) else: display_name = '/r/{}/{}'.format(name, order) - + if name == 'front': - if order == 'hot': + if search: + submissions = reddit.search(search, None, order) + elif order == 'hot': submissions = reddit.get_front_page(limit=None) elif order == 'top': submissions = reddit.get_top(limit=None) @@ -280,10 +282,11 @@ class SubredditContent(BaseContent): submissions = reddit.get_controversial(limit=None) else: raise SubredditError(display_name) - else: subreddit = reddit.get_subreddit(name) - if order == 'hot': + if search: + submissions = reddit.search(search, name, order) + elif order == 'hot': submissions = subreddit.get_hot(limit=None) elif order == 'top': submissions = subreddit.get_top(limit=None) diff --git a/rtv/curses_helpers.py b/rtv/curses_helpers.py index f9a2085..ee9fdaf 100644 --- a/rtv/curses_helpers.py +++ b/rtv/curses_helpers.py @@ -52,12 +52,14 @@ def show_notification(stdscr, message): for index, line in enumerate(message, start=1): window.addstr(index, 1, line) window.refresh() - stdscr.getch() + ch = stdscr.getch() window.clear() window = None stdscr.refresh() + return ch + def show_help(stdscr): """ diff --git a/rtv/docs.py b/rtv/docs.py index b47a31b..988583a 100644 --- a/rtv/docs.py +++ b/rtv/docs.py @@ -2,9 +2,7 @@ from .__version__ import __version__ __all__ = ['AGENT', 'SUMMARY', 'AUTH', 'CONTROLS', 'HELP'] -AGENT = """ -desktop:https://github.com/michael-lazar/rtv:{} (by /u/civilization_phaze_3) -""".format(__version__) +AGENT = "desktop:https://github.com/michael-lazar/rtv:{} (by /u/civilization_phaze_3)".format(__version__) SUMMARY = """ Reddit Terminal Viewer is a lightweight browser for www.reddit.com built into a @@ -33,11 +31,13 @@ Global Commands `r` : Refresh the current page `q` : Quit the program `ENTER` or `o` : Open the selected item in the default web browser + `u` : Log in `?` : Show this help message Subreddit Mode `RIGHT` or `l` : View comments for the selected submission `/` : Open a prompt to switch subreddits + `f` : Open a prompt to search the current subreddit Submission Mode `LEFT` or `h` : Return to subreddit mode diff --git a/rtv/page.py b/rtv/page.py index be55910..70d13f6 100644 --- a/rtv/page.py +++ b/rtv/page.py @@ -5,7 +5,8 @@ import sys import praw.errors from .helpers import clean -from .curses_helpers import Color, show_notification, show_help +from .curses_helpers import Color, show_notification, show_help, text_input +from .docs import AGENT __all__ = ['Navigator'] @@ -226,6 +227,61 @@ class BasePage(object): except praw.errors.LoginOrScopeRequired: show_notification(self.stdscr, ['Login to vote']) + @BaseController.register('u') + def login(self): + """ + Prompt to log into the user's account. Log out if the user is already + logged in. + """ + + if self.reddit.is_logged_in(): + self.logout() + return + + username = self.prompt_input('Enter username:') + password = self.prompt_input('Enter password:', hide=True) + if not username or not password: + curses.flash() + return + + try: + self.reddit.login(username, password) + except praw.errors.InvalidUserPass: + show_notification(self.stdscr, ['Invalid user/pass']) + else: + show_notification(self.stdscr, ['Logged in']) + + def logout(self): + """ + Prompt to log out of the user's account. + """ + + ch = self.prompt_input("Log out? (y/n):") + if ch == 'y': + self.reddit.clear_authentication() + show_notification(self.stdscr, ['Logged out']) + elif ch != 'n': + curses.flash() + + def prompt_input(self, prompt, hide=False): + """Prompt the user for input""" + attr = curses.A_BOLD | Color.CYAN + n_rows, n_cols = self.stdscr.getmaxyx() + + if hide: + prompt += ' ' * (n_cols - len(prompt) - 1) + self.stdscr.addstr(n_rows-1, 0, prompt, attr) + out = self.stdscr.getstr(n_rows-1, 1) + else: + self.stdscr.addstr(n_rows - 1, 0, prompt, attr) + self.stdscr.refresh() + window = self.stdscr.derwin(1, n_cols - len(prompt), + n_rows - 1, len(prompt)) + window.attrset(attr) + out = text_input(window) + + return out + def draw(self): n_rows, n_cols = self.stdscr.getmaxyx() diff --git a/rtv/submission.py b/rtv/submission.py index f8bfa95..ee7aa63 100644 --- a/rtv/submission.py +++ b/rtv/submission.py @@ -143,13 +143,13 @@ class SubmissionPage(BasePage): row = offset if row in valid_rows: - text = clean('{author} '.format(**data)) + text = clean(u'{author} '.format(**data)) attr = curses.A_BOLD attr |= (Color.BLUE if not data['is_author'] else Color.GREEN) win.addnstr(row, 1, text, n_cols - 1, attr) if data['flair']: - text = clean('{flair} '.format(**data)) + text = clean(u'{flair} '.format(**data)) attr = curses.A_BOLD | Color.YELLOW win.addnstr(text, n_cols - win.getyx()[1], attr) @@ -161,7 +161,7 @@ class SubmissionPage(BasePage): text, attr = DARROW, (curses.A_BOLD | Color.RED) win.addnstr(text, n_cols - win.getyx()[1], attr) - text = clean(' {score} {created}'.format(**data)) + text = clean(u' {score} {created}'.format(**data)) win.addnstr(text, n_cols - win.getyx()[1]) n_body = len(data['split_body']) @@ -191,9 +191,9 @@ class SubmissionPage(BasePage): n_rows, n_cols = win.getmaxyx() n_cols -= 1 - text = clean('{body}'.format(**data)) + text = clean(u'{body}'.format(**data)) win.addnstr(0, 1, text, n_cols - 1) - text = clean(' [{count}]'.format(**data)) + text = clean(u' [{count}]'.format(**data)) win.addnstr(text, n_cols - win.getyx()[1], curses.A_BOLD) # Unfortunately vline() doesn't support custom color so we have to @@ -209,37 +209,39 @@ class SubmissionPage(BasePage): n_rows, n_cols = win.getmaxyx() n_cols -= 3 # one for each side of the border + one for offset - # Don't print at all if there is not enough room to fit the whole sub - if data['n_rows'] > n_rows: - win.addnstr(0, 0, '(Not enough space to display)', n_cols) - return - for row, text in enumerate(data['split_title'], start=1): text = clean(text) win.addnstr(row, 1, text, n_cols, curses.A_BOLD) row = len(data['split_title']) + 1 attr = curses.A_BOLD | Color.GREEN - text = clean('{author}'.format(**data)) + text = clean(u'{author}'.format(**data)) win.addnstr(row, 1, text, n_cols, attr) attr = curses.A_BOLD | Color.YELLOW - text = clean(' {flair}'.format(**data)) + text = clean(u' {flair}'.format(**data)) win.addnstr(text, n_cols - win.getyx()[1], attr) - text = clean(' {created} {subreddit}'.format(**data)) + text = clean(u' {created} {subreddit}'.format(**data)) win.addnstr(text, n_cols - win.getyx()[1]) row = len(data['split_title']) + 2 attr = curses.A_UNDERLINE | Color.BLUE - text = clean('{url}'.format(**data)) + text = clean(u'{url}'.format(**data)) win.addnstr(row, 1, text, n_cols, attr) - offset = len(data['split_title']) + 3 - for row, text in enumerate(data['split_text'], start=offset): + + # Cut off text if there is not enough room to display the whole post + split_text = data['split_text'] + if data['n_rows'] > n_rows: + cutoff = data['n_rows'] - n_rows + 1 + split_text = split_text[:-cutoff] + split_text.append('(Not enough space to display)') + + for row, text in enumerate(split_text, start=offset): text = clean(text) win.addnstr(row, 1, text, n_cols) - row = len(data['split_title']) + len(data['split_text']) + 3 - text = clean('{score} {comments}'.format(**data)) + row = len(data['split_title']) + len(split_text) + 3 + text = clean(u'{score} {comments}'.format(**data)) win.addnstr(row, 1, text, n_cols, curses.A_BOLD) win.border() diff --git a/rtv/subreddit.py b/rtv/subreddit.py index 5dfe990..85282e8 100644 --- a/rtv/subreddit.py +++ b/rtv/subreddit.py @@ -8,7 +8,7 @@ from .submission import SubmissionPage from .content import SubredditContent from .helpers import clean, open_browser from .curses_helpers import (BULLET, UARROW, DARROW, Color, LoadScreen, - text_input, show_notification) + show_notification) __all__ = ['opened_links', 'SubredditController', 'SubredditPage'] @@ -50,22 +50,27 @@ class SubredditPage(BasePage): else: self.nav = Navigator(self.content.get) + @SubredditController.register('f') + def search_subreddit(self, name=None): + """Open a prompt to search the subreddit""" + name = name or self.content.name + prompt = 'Search this Subreddit: ' + search = self.prompt_input(prompt) + if search is not None: + try: + self.nav.cursor_index = 0 + self.content = SubredditContent.from_name(self.reddit, name, + self.loader, search=search) + except IndexError: # if there are no submissions + show_notification(self.stdscr, ['No results found']) + @SubredditController.register('/') def prompt_subreddit(self): - "Open a prompt to type in a new subreddit" - - attr = curses.A_BOLD | Color.CYAN + """Open a prompt to type in a new subreddit""" prompt = 'Enter Subreddit: /r/' - n_rows, n_cols = self.stdscr.getmaxyx() - self.stdscr.addstr(n_rows - 1, 0, prompt, attr) - self.stdscr.refresh() - window = self.stdscr.derwin(1, n_cols - len(prompt), - n_rows - 1, len(prompt)) - window.attrset(attr) - - out = text_input(window) - if out is not None: - self.refresh_content(name=out) + name = self.prompt_input(prompt) + if name is not None: + self.refresh_content(name=name) @SubredditController.register(curses.KEY_RIGHT, 'l') def open_submission(self): @@ -110,12 +115,12 @@ class SubredditPage(BasePage): seen = (data['url_full'] in opened_links) link_color = Color.MAGENTA if seen else Color.BLUE attr = curses.A_UNDERLINE | link_color - text = clean('{url}'.format(**data)) + text = clean(u'{url}'.format(**data)) win.addnstr(row, 1, text, n_cols - 1, attr) row = n_title + offset + 1 if row in valid_rows: - text = clean('{score} '.format(**data)) + text = clean(u'{score} '.format(**data)) win.addnstr(row, 1, text, n_cols - 1) if data['likes'] is None: @@ -126,14 +131,14 @@ class SubredditPage(BasePage): text, attr = DARROW, curses.A_BOLD | Color.RED win.addnstr(text, n_cols - win.getyx()[1], attr) - text = clean(' {created} {comments}'.format(**data)) + text = clean(u' {created} {comments}'.format(**data)) win.addnstr(text, n_cols - win.getyx()[1]) row = n_title + offset + 2 if row in valid_rows: - text = clean('{author}'.format(**data)) + text = clean(u'{author}'.format(**data)) win.addnstr(row, 1, text, n_cols - 1, curses.A_BOLD) - text = clean(' {subreddit}'.format(**data)) + text = clean(u' {subreddit}'.format(**data)) win.addnstr(text, n_cols - win.getyx()[1], Color.YELLOW) - text = clean(' {flair}'.format(**data)) + text = clean(u' {flair}'.format(**data)) win.addnstr(text, n_cols - win.getyx()[1], Color.RED)