Semi-major refactor, added upvotes/downvotes.
This commit is contained in:
@@ -10,13 +10,10 @@ from .errors import SubmissionURLError, SubredditNameError
|
||||
|
||||
def split_text(big_text, width):
|
||||
return [
|
||||
text
|
||||
for line in big_text.splitlines()
|
||||
# wrap returns an empty list when "line" is a newline
|
||||
# in order to consider newlines we need a list containing an
|
||||
# empty string
|
||||
for text in (textwrap.wrap(line, width=width) or [''])
|
||||
]
|
||||
text for line in big_text.splitlines()
|
||||
# wrap returns an empty list when "line" is a newline. In order to
|
||||
# consider newlines we need a list containing an empty string.
|
||||
for text in (textwrap.wrap(line, width=width) or [''])]
|
||||
|
||||
def strip_subreddit_url(permalink):
|
||||
"""
|
||||
@@ -64,12 +61,10 @@ class BaseContent(object):
|
||||
def iterate(self, index, step, n_cols):
|
||||
|
||||
while True:
|
||||
|
||||
# Hack to prevent displaying negative indicies if iterating in the
|
||||
# negative direction.
|
||||
if step < 0 and index < 0:
|
||||
# Hack to prevent displaying negative indicies if iterating in
|
||||
# the negative direction.
|
||||
break
|
||||
|
||||
try:
|
||||
yield self.get(index, n_cols=n_cols)
|
||||
except IndexError:
|
||||
@@ -109,6 +104,11 @@ class BaseContent(object):
|
||||
data['object'] = comment
|
||||
data['level'] = comment.nested_level
|
||||
|
||||
if getattr(comment.submission, 'author'):
|
||||
sub_author = comment.submission.author.name
|
||||
else:
|
||||
sub_author = '[deleted]'
|
||||
|
||||
if isinstance(comment, praw.objects.MoreComments):
|
||||
data['type'] = 'MoreComments'
|
||||
data['count'] = comment.count
|
||||
@@ -119,9 +119,9 @@ class BaseContent(object):
|
||||
data['created'] = humanize_timestamp(comment.created_utc)
|
||||
data['score'] = '{} pts'.format(comment.score)
|
||||
data['author'] = (comment.author.name if getattr(comment, 'author') else '[deleted]')
|
||||
sub_author = (comment.submission.author.name if getattr(comment.submission, 'author') else '[deleted]')
|
||||
data['is_author'] = (data['author'] == sub_author)
|
||||
data['flair'] = (comment.author_flair_text if comment.author_flair_text else '')
|
||||
data['likes'] = comment.likes
|
||||
|
||||
return data
|
||||
|
||||
@@ -148,6 +148,7 @@ class BaseContent(object):
|
||||
data['flair'] = (sub.link_flair_text if sub.link_flair_text else '')
|
||||
data['url_full'] = sub.url
|
||||
data['url'] = ('selfpost' if is_selfpost(sub.url) else sub.url)
|
||||
data['likes'] = sub.likes
|
||||
|
||||
return data
|
||||
|
||||
|
||||
10
rtv/main.py
10
rtv/main.py
@@ -5,9 +5,8 @@ import praw
|
||||
from requests.exceptions import ConnectionError, HTTPError
|
||||
from praw.errors import InvalidUserPass
|
||||
|
||||
from . import utils
|
||||
from .errors import SubmissionURLError, SubredditNameError
|
||||
from .utils import curses_session, load_config, HELP
|
||||
from .utils import Symbol, curses_session, load_config, HELP
|
||||
from .subreddit import SubredditPage
|
||||
from .submission import SubmissionPage
|
||||
|
||||
@@ -28,7 +27,7 @@ terminal window.
|
||||
|
||||
EPILOG = """
|
||||
Controls
|
||||
-----
|
||||
--------
|
||||
RTV currently supports browsing both subreddits and individual submissions.
|
||||
In each mode the controls are slightly different. In subreddit mode you can
|
||||
browse through the top submissions on either the front page or a specific
|
||||
@@ -45,7 +44,7 @@ def main():
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.add_argument('-s', dest='subreddit', help='subreddit name')
|
||||
parser.add_argument('-l', dest='link', help='full link to a submission')
|
||||
parser.add_argument('--unicode', help='enable unicode (beta)',
|
||||
parser.add_argument('--unicode', help='enable unicode (experimental)',
|
||||
action='store_true')
|
||||
|
||||
group = parser.add_argument_group(
|
||||
@@ -64,7 +63,7 @@ def main():
|
||||
if getattr(args, key) is None:
|
||||
setattr(args, key, val)
|
||||
|
||||
utils.UNICODE = args.unicode
|
||||
Symbol.UNICODE = args.unicode
|
||||
|
||||
if args.subreddit is None:
|
||||
args.subreddit = 'front'
|
||||
@@ -75,6 +74,7 @@ def main():
|
||||
reddit.config.decode_html_entities = True
|
||||
|
||||
if args.username:
|
||||
# PRAW will prompt for password if it is None
|
||||
reddit.login(args.username, args.password)
|
||||
|
||||
with curses_session() as stdscr:
|
||||
|
||||
47
rtv/page.py
47
rtv/page.py
@@ -1,6 +1,6 @@
|
||||
import curses
|
||||
|
||||
from .utils import Color, clean
|
||||
from .utils import Color, Symbol
|
||||
|
||||
class Navigator(object):
|
||||
"""
|
||||
@@ -129,14 +129,41 @@ class BasePage(object):
|
||||
continue
|
||||
self.stdscr.nodelay(0)
|
||||
|
||||
def upvote(self):
|
||||
|
||||
data = self.content.get(self.nav.absolute_index)
|
||||
if 'likes' not in data:
|
||||
curses.flash()
|
||||
|
||||
elif data['likes']:
|
||||
data['object'].clear_vote()
|
||||
data['likes'] = None
|
||||
else:
|
||||
data['object'].upvote()
|
||||
data['likes'] = True
|
||||
|
||||
def downvote(self):
|
||||
|
||||
data = self.content.get(self.nav.absolute_index)
|
||||
if 'likes' not in data:
|
||||
curses.flash()
|
||||
|
||||
if data['likes'] is False:
|
||||
data['object'].clear_vote()
|
||||
data['likes'] = None
|
||||
else:
|
||||
data['object'].downvote()
|
||||
data['likes'] = False
|
||||
|
||||
def draw(self):
|
||||
|
||||
n_rows, n_cols = self.stdscr.getmaxyx()
|
||||
if n_rows < self.MIN_HEIGHT or n_cols < self.MIN_WIDTH:
|
||||
return
|
||||
|
||||
# Note: 2 argument form of derwin breaks PDcurses on Windows 7!
|
||||
self._header_window = self.stdscr.derwin(1, n_cols, 0, 0)
|
||||
self._content_window = self.stdscr.derwin(1, 0)
|
||||
self._content_window = self.stdscr.derwin(n_rows-1, n_cols, 1, 0)
|
||||
|
||||
self.stdscr.erase()
|
||||
self._draw_header()
|
||||
@@ -157,7 +184,7 @@ class BasePage(object):
|
||||
self._header_window.bkgd(' ', attr)
|
||||
|
||||
sub_name = self.content.name.replace('/r/front', 'Front Page ')
|
||||
self._header_window.addnstr(0, 0, clean(sub_name), n_cols-1)
|
||||
self._header_window.addnstr(0, 0, Symbol.clean(sub_name), n_cols-1)
|
||||
|
||||
if self.reddit.user is not None:
|
||||
username = self.reddit.user.name
|
||||
@@ -165,7 +192,7 @@ class BasePage(object):
|
||||
# Only print the username if it fits in the empty space on the right
|
||||
if (s_col - 1) >= len(sub_name):
|
||||
n = (n_cols - s_col - 1)
|
||||
self._header_window.addnstr(0, s_col, clean(username), n)
|
||||
self._header_window.addnstr(0, s_col, Symbol.clean(username), n)
|
||||
|
||||
self._header_window.refresh()
|
||||
|
||||
@@ -215,14 +242,11 @@ class BasePage(object):
|
||||
self.remove_cursor()
|
||||
|
||||
valid, redraw = self.nav.move(direction, len(self._subwindows))
|
||||
if not valid:
|
||||
curses.flash()
|
||||
|
||||
# TODO: If we don't redraw, ACS_VLINE gets screwed up when changing the
|
||||
# attr back to normal. There may be a way around this.
|
||||
if True: #if redraw
|
||||
self._draw_content()
|
||||
if not valid: curses.flash()
|
||||
|
||||
# Note: ACS_VLINE doesn't like changing the attribute, so always redraw.
|
||||
# if redraw: self._draw_content()
|
||||
self._draw_content()
|
||||
self.add_cursor()
|
||||
|
||||
def _edit_cursor(self, attribute=None):
|
||||
@@ -231,7 +255,6 @@ class BasePage(object):
|
||||
if self.nav.absolute_index < 0:
|
||||
return
|
||||
|
||||
# TODO: attach attr to data[attr] or something
|
||||
window, attr = self._subwindows[self.nav.cursor_index]
|
||||
if attr is not None:
|
||||
attribute |= attr
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import curses
|
||||
import sys
|
||||
import webbrowser
|
||||
|
||||
import six
|
||||
|
||||
from .content import SubmissionContent
|
||||
from .page import BasePage
|
||||
from .utils import LoadScreen, Color, ESCAPE, display_help, open_new_tab, clean
|
||||
from .utils import LoadScreen, Color, Symbol, display_help, open_browser
|
||||
|
||||
class SubmissionPage(BasePage):
|
||||
|
||||
@@ -42,6 +39,9 @@ class SubmissionPage(BasePage):
|
||||
self.toggle_comment()
|
||||
self.draw()
|
||||
|
||||
elif cmd in (curses.KEY_LEFT, ord('h')):
|
||||
break
|
||||
|
||||
elif cmd == ord('o'):
|
||||
self.open_link()
|
||||
self.draw()
|
||||
@@ -54,18 +54,20 @@ class SubmissionPage(BasePage):
|
||||
display_help(self.stdscr)
|
||||
self.draw()
|
||||
|
||||
elif cmd == ord('a'):
|
||||
self.upvote()
|
||||
self.draw()
|
||||
|
||||
elif cmd == ord('z'):
|
||||
self.downvote()
|
||||
self.draw()
|
||||
|
||||
elif cmd == ord('q'):
|
||||
sys.exit()
|
||||
|
||||
elif cmd == curses.KEY_RESIZE:
|
||||
self.draw()
|
||||
|
||||
elif cmd in (ESCAPE, curses.KEY_LEFT, ord('h')):
|
||||
break
|
||||
|
||||
else:
|
||||
curses.beep()
|
||||
|
||||
def toggle_comment(self):
|
||||
|
||||
self.content.toggle(self.nav.absolute_index)
|
||||
@@ -81,19 +83,16 @@ class SubmissionPage(BasePage):
|
||||
# Always open the page for the submission
|
||||
# May want to expand at some point to open comment permalinks
|
||||
url = self.content.get(-1)['permalink']
|
||||
open_new_tab(url)
|
||||
open_browser(url)
|
||||
|
||||
def draw_item(self, win, data, inverted=False):
|
||||
|
||||
if data['type'] == 'MoreComments':
|
||||
return self.draw_more_comments(win, data)
|
||||
|
||||
elif data['type'] == 'HiddenComment':
|
||||
return self.draw_more_comments(win, data)
|
||||
|
||||
elif data['type'] == 'Comment':
|
||||
return self.draw_comment(win, data, inverted=inverted)
|
||||
|
||||
else:
|
||||
return self.draw_submission(win, data)
|
||||
|
||||
@@ -109,28 +108,39 @@ class SubmissionPage(BasePage):
|
||||
|
||||
row = offset
|
||||
if row in valid_rows:
|
||||
text = '{author}'.format(**data)
|
||||
|
||||
text = Symbol.clean('{author} '.format(**data))
|
||||
attr = curses.A_BOLD
|
||||
attr |= (Color.BLUE if not data['is_author'] else Color.GREEN)
|
||||
win.addnstr(row, 1, clean(text), n_cols-1, attr)
|
||||
text = ' {flair}'.format(**data)
|
||||
win.addnstr(clean(text), n_cols-win.getyx()[1], curses.A_BOLD | Color.YELLOW)
|
||||
text = ' {score} {created}'.format(**data)
|
||||
win.addnstr(clean(text), n_cols - win.getyx()[1])
|
||||
win.addnstr(row, 1, text, n_cols-1, attr)
|
||||
|
||||
if data['flair']:
|
||||
text = Symbol.clean('{flair} '.format(**data))
|
||||
attr = curses.A_BOLD | Color.YELLOW
|
||||
win.addnstr(text, n_cols-win.getyx()[1], attr)
|
||||
|
||||
if data['likes'] is None:
|
||||
text, attr = Symbol.BULLET, curses.A_BOLD
|
||||
elif data['likes']:
|
||||
text, attr = Symbol.UARROW, (curses.A_BOLD | Color.GREEN)
|
||||
else:
|
||||
text, attr = Symbol.DARROW, (curses.A_BOLD | Color.RED)
|
||||
win.addnstr(text, n_cols-win.getyx()[1], attr)
|
||||
|
||||
text = Symbol.clean(' {score} {created}'.format(**data))
|
||||
win.addnstr(text, n_cols-win.getyx()[1])
|
||||
|
||||
n_body = len(data['split_body'])
|
||||
for row, text in enumerate(data['split_body'], start=offset+1):
|
||||
if row in valid_rows:
|
||||
win.addnstr(row, 1, clean(text), n_cols-1)
|
||||
text = Symbol.clean(text)
|
||||
win.addnstr(row, 1, text, n_cols-1)
|
||||
|
||||
# Vertical line, unfortunately vline() doesn't support custom color so
|
||||
# we have to build it one chr at a time.
|
||||
# Unfortunately vline() doesn't support custom color so we have to
|
||||
# build it one segment at a time.
|
||||
attr = Color.get_level(data['level'])
|
||||
for y in range(n_rows):
|
||||
|
||||
x = 0
|
||||
|
||||
# Nobody pays attention to curses ;(
|
||||
# http://bugs.python.org/issue21088
|
||||
if (sys.version_info.major,
|
||||
sys.version_info.minor,
|
||||
@@ -139,23 +149,25 @@ class SubmissionPage(BasePage):
|
||||
|
||||
win.addch(y, x, curses.ACS_VLINE, attr)
|
||||
|
||||
return attr | curses.ACS_VLINE
|
||||
return (attr | curses.ACS_VLINE)
|
||||
|
||||
@staticmethod
|
||||
def draw_more_comments(win, data):
|
||||
|
||||
n_rows, n_cols = win.getmaxyx()
|
||||
n_cols -= 1
|
||||
text = '{body}'.format(**data)
|
||||
win.addnstr(0, 1, clean(text), n_cols-1)
|
||||
text = ' [{count}]'.format(**data)
|
||||
win.addnstr(clean(text), n_cols - win.getyx()[1], curses.A_BOLD)
|
||||
|
||||
text = Symbol.clean('{body}'.format(**data))
|
||||
win.addnstr(0, 1, text, n_cols-1)
|
||||
text = Symbol.clean(' [{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
|
||||
# build it one segment at a time.
|
||||
attr = Color.get_level(data['level'])
|
||||
for y in range(n_rows):
|
||||
win.addch(y, 0, curses.ACS_VLINE, attr)
|
||||
win.addch(0, 0, curses.ACS_VLINE, attr)
|
||||
|
||||
return attr | curses.ACS_VLINE
|
||||
return (attr | curses.ACS_VLINE)
|
||||
|
||||
@staticmethod
|
||||
def draw_submission(win, data):
|
||||
@@ -169,28 +181,31 @@ class SubmissionPage(BasePage):
|
||||
return
|
||||
|
||||
for row, text in enumerate(data['split_title'], start=1):
|
||||
win.addnstr(row, 1, clean(text), n_cols, curses.A_BOLD)
|
||||
text = Symbol.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 = '{author}'.format(**data)
|
||||
win.addnstr(row, 1, clean(text), n_cols, attr)
|
||||
text = ' {flair}'.format(**data)
|
||||
win.addnstr(clean(text), n_cols-win.getyx()[1], curses.A_BOLD | Color.YELLOW)
|
||||
text = ' {created} {subreddit}'.format(**data)
|
||||
win.addnstr(clean(text), n_cols - win.getyx()[1])
|
||||
text = Symbol.clean('{author}'.format(**data))
|
||||
win.addnstr(row, 1, text, n_cols, attr)
|
||||
attr = curses.A_BOLD | Color.YELLOW
|
||||
text = Symbol.clean(' {flair}'.format(**data))
|
||||
win.addnstr(text, n_cols-win.getyx()[1], attr)
|
||||
text = Symbol.clean(' {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 = '{url}'.format(**data)
|
||||
win.addnstr(row, 1, clean(text), n_cols, attr)
|
||||
text = Symbol.clean('{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):
|
||||
win.addnstr(row, 1, clean(text), n_cols)
|
||||
text = Symbol.clean(text)
|
||||
win.addnstr(row, 1, text, n_cols)
|
||||
|
||||
row = len(data['split_title']) + len(data['split_text']) + 3
|
||||
text = '{score} {comments}'.format(**data)
|
||||
win.addnstr(row, 1, clean(text), n_cols, curses.A_BOLD)
|
||||
text = Symbol.clean('{score} {comments}'.format(**data))
|
||||
win.addnstr(row, 1, text, n_cols, curses.A_BOLD)
|
||||
|
||||
win.border()
|
||||
@@ -7,9 +7,8 @@ from .errors import SubredditNameError
|
||||
from .page import BasePage
|
||||
from .submission import SubmissionPage
|
||||
from .content import SubredditContent
|
||||
from .utils import (LoadScreen, Color, text_input, display_message,
|
||||
display_help, open_new_tab, clean)
|
||||
|
||||
from .utils import (LoadScreen, Symbol, Color, text_input, display_message,
|
||||
display_help, open_browser)
|
||||
|
||||
# Used to keep track of browsing history across the current session
|
||||
_opened_links = set()
|
||||
@@ -54,6 +53,14 @@ class SubredditPage(BasePage):
|
||||
display_help(self.stdscr)
|
||||
self.draw()
|
||||
|
||||
elif cmd == ord('a'):
|
||||
self.upvote()
|
||||
self.draw()
|
||||
|
||||
elif cmd == ord('z'):
|
||||
self.downvote()
|
||||
self.draw()
|
||||
|
||||
elif cmd == ord('q'):
|
||||
sys.exit()
|
||||
|
||||
@@ -64,9 +71,6 @@ class SubredditPage(BasePage):
|
||||
self.prompt_subreddit()
|
||||
self.draw()
|
||||
|
||||
else:
|
||||
curses.beep()
|
||||
|
||||
def refresh_content(self, name=None):
|
||||
|
||||
name = name or self.content.name
|
||||
@@ -75,19 +79,23 @@ class SubredditPage(BasePage):
|
||||
self.content = SubredditContent.from_name(
|
||||
self.reddit, name, self.loader)
|
||||
|
||||
except (SubredditNameError, HTTPError):
|
||||
display_message(self.stdscr, ['Invalid Subreddit'])
|
||||
except SubredditNameError:
|
||||
display_message(self.stdscr, ['Invalid subreddit'])
|
||||
|
||||
except HTTPError:
|
||||
display_message(self.stdscr, ['Could not reach subreddit'])
|
||||
|
||||
else:
|
||||
self.nav.page_index, self.nav.cursor_index = 0, 0
|
||||
self.nav.inverted = False
|
||||
|
||||
def prompt_subreddit(self):
|
||||
"Open a prompt to type in a new subreddit"
|
||||
|
||||
attr = curses.A_BOLD | Color.CYAN
|
||||
prompt = 'Enter Subreddit: /r/'
|
||||
n_rows, n_cols = self.stdscr.getmaxyx()
|
||||
self.stdscr.addstr(n_rows-1, 0, clean(prompt), attr)
|
||||
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)
|
||||
@@ -108,9 +116,10 @@ class SubredditPage(BasePage):
|
||||
_opened_links.add(data['url_full'])
|
||||
|
||||
def open_link(self):
|
||||
"Open a link with the webbrowser"
|
||||
|
||||
url = self.content.get(self.nav.absolute_index)['url_full']
|
||||
open_new_tab(url)
|
||||
open_browser(url)
|
||||
|
||||
global _opened_links
|
||||
_opened_links.add(url)
|
||||
@@ -128,27 +137,38 @@ class SubredditPage(BasePage):
|
||||
n_title = len(data['split_title'])
|
||||
for row, text in enumerate(data['split_title'], start=offset):
|
||||
if row in valid_rows:
|
||||
attr = curses.A_BOLD
|
||||
win.addstr(row, 1, clean(text), attr)
|
||||
text = Symbol.clean(text)
|
||||
win.addnstr(row, 1, text, n_cols-1, curses.A_BOLD)
|
||||
|
||||
row = n_title + offset
|
||||
if row in valid_rows:
|
||||
seen = (data['url_full'] in _opened_links)
|
||||
link_color = Color.MAGENTA if seen else Color.BLUE
|
||||
attr = curses.A_UNDERLINE | link_color
|
||||
text = '{url}'.format(**data)
|
||||
win.addnstr(row, 1, clean(text), n_cols-1, attr)
|
||||
text = Symbol.clean('{url}'.format(**data))
|
||||
win.addnstr(row, 1, text, n_cols-1, attr)
|
||||
|
||||
row = n_title + offset + 1
|
||||
if row in valid_rows:
|
||||
text = '{created} {comments} {score}'.format(**data)
|
||||
win.addnstr(row, 1, clean(text), n_cols-1)
|
||||
text = Symbol.clean('{score} '.format(**data))
|
||||
win.addnstr(row, 1, text, n_cols-1)
|
||||
|
||||
if data['likes'] is None:
|
||||
text, attr = Symbol.BULLET, curses.A_BOLD
|
||||
elif data['likes']:
|
||||
text, attr = Symbol.UARROW, curses.A_BOLD | Color.GREEN
|
||||
else:
|
||||
text, attr = Symbol.DARROW, curses.A_BOLD | Color.RED
|
||||
win.addnstr(text, n_cols-win.getyx()[1], attr)
|
||||
|
||||
text = Symbol.clean(' {created} {comments}'.format(**data))
|
||||
win.addnstr(text, n_cols-win.getyx()[1])
|
||||
|
||||
row = n_title + offset + 2
|
||||
if row in valid_rows:
|
||||
text = '{author}'.format(**data)
|
||||
win.addnstr(row, 1, clean(text), n_cols-1, curses.A_BOLD)
|
||||
text = ' {subreddit}'.format(**data)
|
||||
win.addnstr(clean(text), n_cols - win.getyx()[1], Color.YELLOW)
|
||||
text = ' {flair}'.format(**data)
|
||||
win.addnstr(clean(text), n_cols - win.getyx()[1], Color.RED)
|
||||
text = Symbol.clean('{author}'.format(**data))
|
||||
win.addnstr(row, 1, text, n_cols-1, curses.A_BOLD)
|
||||
text = Symbol.clean(' {subreddit}'.format(**data))
|
||||
win.addnstr(text, n_cols-win.getyx()[1], Color.YELLOW)
|
||||
text = Symbol.clean(' {flair}'.format(**data))
|
||||
win.addnstr(text, n_cols-win.getyx()[1], Color.RED)
|
||||
|
||||
60
rtv/utils.py
60
rtv/utils.py
@@ -12,9 +12,6 @@ from six.moves import configparser
|
||||
|
||||
from .errors import EscapePressed
|
||||
|
||||
FORCE_ASCII = True
|
||||
ESCAPE = 27
|
||||
|
||||
HELP = """
|
||||
Global Commands
|
||||
`UP/DOWN` or `j/k` : Scroll to the prev/next item
|
||||
@@ -32,30 +29,43 @@ Submission Mode
|
||||
`RIGHT` or `l` : Fold the selected comment, or load additional comments
|
||||
"""
|
||||
|
||||
def clean(string):
|
||||
"""
|
||||
Required reading!
|
||||
http://nedbatchelder.com/text/unipain.html
|
||||
class Symbol(object):
|
||||
|
||||
Python 2 input string will be a unicode type (unicode code points). Curses
|
||||
will accept that if all of the points are in the ascii range. However, if
|
||||
any of the code points are not valid ascii curses will throw a
|
||||
UnicodeEncodeError: 'ascii' codec can't encode character, ordinal not in
|
||||
range(128). However, if we encode the unicode to a utf-8 byte string and
|
||||
pass that to curses, curses will render correctly.
|
||||
UNICODE = False
|
||||
|
||||
Python 3 input string will be a string type (unicode code points). Curses
|
||||
will accept that in all cases. However, the n character count in addnstr
|
||||
will get screwed up.
|
||||
ESCAPE = 27
|
||||
|
||||
"""
|
||||
if six.PY2:
|
||||
string = string.encode('utf-8', 'replace')
|
||||
else:
|
||||
string = string.encode('utf-8', 'replace')
|
||||
pass
|
||||
# Curses does define constants for these (e.g. curses.ACS_BULLET)
|
||||
# However, they rely on using the curses.addch() function, which has been
|
||||
# found to be buggy and a PITA to work with. By defining them as unicode
|
||||
# points they can be added via the more reliable curses.addstr().
|
||||
# http://bugs.python.org/issue21088
|
||||
UARROW = u'\u25b2'.encode('utf-8')
|
||||
DARROW = u'\u25bc'.encode('utf-8')
|
||||
BULLET = u'\u2022'.encode('utf-8')
|
||||
|
||||
return string
|
||||
@classmethod
|
||||
def clean(cls, string):
|
||||
"""
|
||||
Required reading!
|
||||
http://nedbatchelder.com/text/unipain.html
|
||||
|
||||
Python 2 input string will be a unicode type (unicode code points). Curses
|
||||
will accept that if all of the points are in the ascii range. However, if
|
||||
any of the code points are not valid ascii curses will throw a
|
||||
UnicodeEncodeError: 'ascii' codec can't encode character, ordinal not in
|
||||
range(128). However, if we encode the unicode to a utf-8 byte string and
|
||||
pass that to curses, curses will render correctly.
|
||||
|
||||
Python 3 input string will be a string type (unicode code points). Curses
|
||||
will accept that in all cases. However, the n character count in addnstr
|
||||
will get screwed up.
|
||||
|
||||
"""
|
||||
|
||||
encoding = 'utf-8' if cls.UNICODE else 'ascii'
|
||||
string = string.encode(encoding, 'replace')
|
||||
return string
|
||||
|
||||
class Color(object):
|
||||
|
||||
@@ -123,7 +133,7 @@ def text_input(window):
|
||||
def validate(ch):
|
||||
"Filters characters for special key sequences"
|
||||
|
||||
if ch == ESCAPE:
|
||||
if ch == Symbol.ESCAPE:
|
||||
raise EscapePressed
|
||||
|
||||
# Fix backspace for iterm
|
||||
@@ -245,7 +255,7 @@ class LoadScreen(object):
|
||||
window.refresh()
|
||||
time.sleep(interval)
|
||||
|
||||
def open_new_tab(url):
|
||||
def open_browser(url):
|
||||
"""
|
||||
Call webbrowser.open_new_tab(url) and redirect stdout/stderr to devnull.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user