Merge branch 'master' into themes
# Conflicts: # rtv/__main__.py # rtv/page.py # rtv/submission_page.py # rtv/subreddit_page.py # rtv/subscription_page.py # rtv/terminal.py # rtv/theme.py
This commit is contained in:
@@ -193,23 +193,23 @@ class OAuthHelper(object):
|
|||||||
# If an exception is raised it will be seen by the thread
|
# If an exception is raised it will be seen by the thread
|
||||||
# so we don't need to explicitly shutdown() the server
|
# so we don't need to explicitly shutdown() the server
|
||||||
_logger.exception(e)
|
_logger.exception(e)
|
||||||
self.term.show_notification('Browser Error')
|
self.term.show_notification('Browser Error', style='error')
|
||||||
else:
|
else:
|
||||||
self.server.shutdown()
|
self.server.shutdown()
|
||||||
finally:
|
finally:
|
||||||
thread.join()
|
thread.join()
|
||||||
|
|
||||||
if self.params['error'] == 'access_denied':
|
if self.params['error'] == 'access_denied':
|
||||||
self.term.show_notification('Denied access')
|
self.term.show_notification('Denied access', style='error')
|
||||||
return
|
return
|
||||||
elif self.params['error']:
|
elif self.params['error']:
|
||||||
self.term.show_notification('Authentication error')
|
self.term.show_notification('Authentication error', style='error')
|
||||||
return
|
return
|
||||||
elif self.params['state'] is None:
|
elif self.params['state'] is None:
|
||||||
# Something went wrong but it's not clear what happened
|
# Something went wrong but it's not clear what happened
|
||||||
return
|
return
|
||||||
elif self.params['state'] != state:
|
elif self.params['state'] != state:
|
||||||
self.term.show_notification('UUID mismatch')
|
self.term.show_notification('UUID mismatch', style='error')
|
||||||
return
|
return
|
||||||
|
|
||||||
with self.term.loader('Logging in'):
|
with self.term.loader('Logging in'):
|
||||||
|
|||||||
19
rtv/page.py
19
rtv/page.py
@@ -4,7 +4,6 @@ from __future__ import unicode_literals
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import curses
|
|
||||||
import logging
|
import logging
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ class Page(object):
|
|||||||
def refresh_content(self, order=None, name=None):
|
def refresh_content(self, order=None, name=None):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def _draw_item(self, window, data, inverted, highlight):
|
def _draw_item(self, window, data, inverted):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def get_selected_item(self):
|
def get_selected_item(self):
|
||||||
@@ -467,7 +466,8 @@ class Page(object):
|
|||||||
if self.content.order is not None:
|
if self.content.order is not None:
|
||||||
order = self.content.order.split('-')[0]
|
order = self.content.order.split('-')[0]
|
||||||
col = text.find(order) - 3
|
col = text.find(order) - 3
|
||||||
window.chgat(0, col, 3, self.term.attr('order_bar', True))
|
attr = self.term.theme.get('order_bar', modifier='selected')
|
||||||
|
window.chgat(0, col, 3, attr)
|
||||||
|
|
||||||
self._row += 1
|
self._row += 1
|
||||||
|
|
||||||
@@ -536,12 +536,15 @@ class Page(object):
|
|||||||
# Now that the windows are setup, we can take a second pass through
|
# Now that the windows are setup, we can take a second pass through
|
||||||
# to draw the content
|
# to draw the content
|
||||||
for index, (win, data, inverted) in enumerate(self._subwindows):
|
for index, (win, data, inverted) in enumerate(self._subwindows):
|
||||||
highlight = (index == self.nav.cursor_index)
|
if index == self.nav.cursor_index:
|
||||||
if highlight:
|
# This lets the theme know to invert the cursor
|
||||||
win.bkgd(str(' '), self.term.attr('@highlight'))
|
modifier = 'selected'
|
||||||
else:
|
else:
|
||||||
win.bkgd(str(' '), self.term.attr('@normal'))
|
modifier = None
|
||||||
self._draw_item(win, data, inverted, highlight)
|
|
||||||
|
with self.term.theme.set_modifier(modifier):
|
||||||
|
win.bkgd(str(' '), self.term.attr('normal'))
|
||||||
|
self._draw_item(win, data, inverted)
|
||||||
|
|
||||||
self._row += win_n_rows
|
self._row += win_n_rows
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import curses
|
|
||||||
|
|
||||||
from . import docs
|
from . import docs
|
||||||
from .content import SubmissionContent, SubredditContent
|
from .content import SubmissionContent, SubredditContent
|
||||||
@@ -264,16 +263,18 @@ class SubmissionPage(Page):
|
|||||||
|
|
||||||
self.clear_input_queue()
|
self.clear_input_queue()
|
||||||
|
|
||||||
def _draw_item(self, win, data, inverted, highlight):
|
def _draw_item(self, win, data, inverted):
|
||||||
|
|
||||||
if data['type'] in ('MoreComments', 'HiddenComment'):
|
if data['type'] == 'MoreComments':
|
||||||
self._draw_more_comments(win, data, highlight)
|
return self._draw_more_comments(win, data)
|
||||||
|
elif data['type'] == 'HiddenComment':
|
||||||
|
return self._draw_more_comments(win, data)
|
||||||
elif data['type'] == 'Comment':
|
elif data['type'] == 'Comment':
|
||||||
self._draw_comment(win, data, inverted, highlight)
|
return self._draw_comment(win, data, inverted)
|
||||||
else:
|
else:
|
||||||
self._draw_submission(win, data, highlight)
|
return self._draw_submission(win, data)
|
||||||
|
|
||||||
def _draw_comment(self, win, data, inverted, highlight):
|
def _draw_comment(self, win, data, inverted):
|
||||||
|
|
||||||
n_rows, n_cols = win.getmaxyx()
|
n_rows, n_cols = win.getmaxyx()
|
||||||
n_cols -= 1
|
n_cols -= 1
|
||||||
@@ -287,7 +288,7 @@ class SubmissionPage(Page):
|
|||||||
split_body = data['split_body']
|
split_body = data['split_body']
|
||||||
if data['n_rows'] > n_rows:
|
if data['n_rows'] > n_rows:
|
||||||
# Only when there is a single comment on the page and not inverted
|
# Only when there is a single comment on the page and not inverted
|
||||||
if not inverted and len(self._subwindows) == 0:
|
if not inverted and len(self._subwindows) == 1:
|
||||||
cutoff = data['n_rows'] - n_rows + 1
|
cutoff = data['n_rows'] - n_rows + 1
|
||||||
split_body = split_body[:-cutoff]
|
split_body = split_body[:-cutoff]
|
||||||
split_body.append('(Not enough space to display)')
|
split_body.append('(Not enough space to display)')
|
||||||
@@ -295,104 +296,104 @@ class SubmissionPage(Page):
|
|||||||
row = offset
|
row = offset
|
||||||
if row in valid_rows:
|
if row in valid_rows:
|
||||||
if data['is_author']:
|
if data['is_author']:
|
||||||
attr = self.term.attr('comment_author_self', highlight)
|
attr = self.term.attr('comment_author_self')
|
||||||
text = '{author} [S]'.format(**data)
|
text = '{author} [S]'.format(**data)
|
||||||
else:
|
else:
|
||||||
attr = self.term.attr('comment_author', highlight)
|
attr = self.term.attr('comment_author')
|
||||||
text = '{author}'.format(**data)
|
text = '{author}'.format(**data)
|
||||||
self.term.add_line(win, text, row, 1, attr)
|
self.term.add_line(win, text, row, 1, attr)
|
||||||
|
|
||||||
if data['flair']:
|
if data['flair']:
|
||||||
attr = self.term.attr('user_flair', highlight)
|
attr = self.term.attr('user_flair')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, '{flair}'.format(**data), attr=attr)
|
self.term.add_line(win, '{flair}'.format(**data), attr=attr)
|
||||||
|
|
||||||
arrow, attr = self.term.get_arrow(data['likes'], highlight)
|
arrow, attr = self.term.get_arrow(data['likes'])
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, arrow, attr=attr)
|
self.term.add_line(win, arrow, attr=attr)
|
||||||
|
|
||||||
attr = self.term.attr('score', highlight)
|
attr = self.term.attr('score')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, '{score}'.format(**data), attr=attr)
|
self.term.add_line(win, '{score}'.format(**data), attr=attr)
|
||||||
|
|
||||||
attr = self.term.attr('created', highlight)
|
attr = self.term.attr('created')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, '{created}'.format(**data), attr=attr)
|
self.term.add_line(win, '{created}'.format(**data), attr=attr)
|
||||||
|
|
||||||
if data['gold']:
|
if data['gold']:
|
||||||
attr = self.term.attr('gold', highlight)
|
attr = self.term.attr('gold')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, self.term.guilded, attr=attr)
|
self.term.add_line(win, self.term.guilded, attr=attr)
|
||||||
|
|
||||||
if data['stickied']:
|
if data['stickied']:
|
||||||
attr = self.term.attr('stickied', highlight)
|
attr = self.term.attr('stickied')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, '[stickied]', attr=attr)
|
self.term.add_line(win, '[stickied]', attr=attr)
|
||||||
|
|
||||||
if data['saved']:
|
if data['saved']:
|
||||||
attr = self.term.attr('saved', highlight)
|
attr = self.term.attr('saved')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, '[saved]', attr=attr)
|
self.term.add_line(win, '[saved]', attr=attr)
|
||||||
|
|
||||||
for row, text in enumerate(split_body, start=offset+1):
|
for row, text in enumerate(split_body, start=offset+1):
|
||||||
attr = self.term.attr('comment_text', highlight)
|
attr = self.term.attr('comment_text')
|
||||||
if row in valid_rows:
|
if row in valid_rows:
|
||||||
self.term.add_line(win, text, row, 1, attr=attr)
|
self.term.add_line(win, text, row, 1, attr=attr)
|
||||||
|
|
||||||
# Unfortunately vline() doesn't support custom color so we have to
|
# Unfortunately vline() doesn't support custom color so we have to
|
||||||
# build it one segment at a time.
|
# build it one segment at a time.
|
||||||
index = data['level'] % len(self.term.theme.BAR_LEVELS)
|
index = data['level'] % len(self.term.theme.BAR_LEVELS)
|
||||||
attr = self.term.attr(self.term.theme.BAR_LEVELS[index], highlight)
|
attr = self.term.attr(self.term.theme.BAR_LEVELS[index])
|
||||||
for y in range(n_rows):
|
for y in range(n_rows):
|
||||||
self.term.addch(win, y, 0, self.term.vline, attr)
|
self.term.addch(win, y, 0, self.term.vline, attr)
|
||||||
|
|
||||||
def _draw_more_comments(self, win, data, highlight):
|
def _draw_more_comments(self, win, data):
|
||||||
|
|
||||||
n_rows, n_cols = win.getmaxyx()
|
n_rows, n_cols = win.getmaxyx()
|
||||||
n_cols -= 1
|
n_cols -= 1
|
||||||
|
|
||||||
attr = self.term.attr('hidden_comment_text', highlight)
|
attr = self.term.attr('hidden_comment_text')
|
||||||
self.term.add_line(win, '{body}'.format(**data), 0, 1, attr=attr)
|
self.term.add_line(win, '{body}'.format(**data), 0, 1, attr=attr)
|
||||||
|
|
||||||
attr = self.term.attr('hidden_comment_expand', highlight)
|
attr = self.term.attr('hidden_comment_expand')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, '[{count}]'.format(**data), attr=attr)
|
self.term.add_line(win, '[{count}]'.format(**data), attr=attr)
|
||||||
|
|
||||||
index = data['level'] % len(self.term.theme.BAR_LEVELS)
|
index = data['level'] % len(self.term.theme.BAR_LEVELS)
|
||||||
attr = self.term.attr(self.term.theme.BAR_LEVELS[index], highlight)
|
attr = self.term.attr(self.term.theme.BAR_LEVELS[index])
|
||||||
self.term.addch(win, 0, 0, self.term.vline, attr)
|
self.term.addch(win, 0, 0, self.term.vline, attr)
|
||||||
|
|
||||||
def _draw_submission(self, win, data, highlight):
|
def _draw_submission(self, win, data):
|
||||||
|
|
||||||
n_rows, n_cols = win.getmaxyx()
|
n_rows, n_cols = win.getmaxyx()
|
||||||
n_cols -= 3 # one for each side of the border + one for offset
|
n_cols -= 3 # one for each side of the border + one for offset
|
||||||
|
|
||||||
attr = self.term.attr('submission_title', highlight)
|
attr = self.term.attr('submission_title')
|
||||||
for row, text in enumerate(data['split_title'], start=1):
|
for row, text in enumerate(data['split_title'], start=1):
|
||||||
self.term.add_line(win, text, row, 1, attr)
|
self.term.add_line(win, text, row, 1, attr)
|
||||||
|
|
||||||
row = len(data['split_title']) + 1
|
row = len(data['split_title']) + 1
|
||||||
attr = self.term.attr('submission_author', highlight)
|
attr = self.term.attr('submission_author')
|
||||||
self.term.add_line(win, '{author}'.format(**data), row, 1, attr)
|
self.term.add_line(win, '{author}'.format(**data), row, 1, attr)
|
||||||
|
|
||||||
if data['flair']:
|
if data['flair']:
|
||||||
attr = self.term.attr('submission_flair', highlight)
|
attr = self.term.attr('submission_flair')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, '{flair}'.format(**data), attr=attr)
|
self.term.add_line(win, '{flair}'.format(**data), attr=attr)
|
||||||
|
|
||||||
attr = self.term.attr('created', highlight)
|
attr = self.term.attr('created')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, '{created}'.format(**data), attr=attr)
|
self.term.add_line(win, '{created}'.format(**data), attr=attr)
|
||||||
|
|
||||||
attr = self.term.attr('submission_subreddit', highlight)
|
attr = self.term.attr('submission_subreddit')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, '/r/{subreddit}'.format(**data), attr=attr)
|
self.term.add_line(win, '/r/{subreddit}'.format(**data), attr=attr)
|
||||||
|
|
||||||
row = len(data['split_title']) + 2
|
row = len(data['split_title']) + 2
|
||||||
if data['url_full'] in self.config.history:
|
if data['url_full'] in self.config.history:
|
||||||
attr = self.term.attr('url_seen', highlight)
|
attr = self.term.attr('url_seen')
|
||||||
else:
|
else:
|
||||||
attr = self.term.attr('url', highlight)
|
attr = self.term.attr('url')
|
||||||
self.term.add_line(win, '{url}'.format(**data), row, 1, attr)
|
self.term.add_line(win, '{url}'.format(**data), row, 1, attr)
|
||||||
|
|
||||||
offset = len(data['split_title']) + 3
|
offset = len(data['split_title']) + 3
|
||||||
@@ -404,34 +405,34 @@ class SubmissionPage(Page):
|
|||||||
split_text = split_text[:-cutoff]
|
split_text = split_text[:-cutoff]
|
||||||
split_text.append('(Not enough space to display)')
|
split_text.append('(Not enough space to display)')
|
||||||
|
|
||||||
attr = self.term.attr('submission_text', highlight)
|
attr = self.term.attr('submission_text')
|
||||||
for row, text in enumerate(split_text, start=offset):
|
for row, text in enumerate(split_text, start=offset):
|
||||||
self.term.add_line(win, text, row, 1, attr=attr)
|
self.term.add_line(win, text, row, 1, attr=attr)
|
||||||
|
|
||||||
row = len(data['split_title']) + len(split_text) + 3
|
row = len(data['split_title']) + len(split_text) + 3
|
||||||
attr = self.term.attr('score', highlight)
|
attr = self.term.attr('score')
|
||||||
self.term.add_line(win, '{score}'.format(**data), row, 1, attr=attr)
|
self.term.add_line(win, '{score}'.format(**data), row, 1, attr=attr)
|
||||||
|
|
||||||
arrow, attr = self.term.get_arrow(data['likes'], highlight)
|
arrow, attr = self.term.get_arrow(data['likes'])
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, arrow, attr=attr)
|
self.term.add_line(win, arrow, attr=attr)
|
||||||
|
|
||||||
attr = self.term.attr('comment_count', highlight)
|
attr = self.term.attr('comment_count')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, '{comments}'.format(**data), attr=attr)
|
self.term.add_line(win, '{comments}'.format(**data), attr=attr)
|
||||||
|
|
||||||
if data['gold']:
|
if data['gold']:
|
||||||
attr = self.term.attr('gold', highlight)
|
attr = self.term.attr('gold')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, self.term.guilded, attr=attr)
|
self.term.add_line(win, self.term.guilded, attr=attr)
|
||||||
|
|
||||||
if data['nsfw']:
|
if data['nsfw']:
|
||||||
attr = self.term.attr('nsfw', highlight)
|
attr = self.term.attr('nsfw')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, 'NSFW', attr=attr)
|
self.term.add_line(win, 'NSFW', attr=attr)
|
||||||
|
|
||||||
if data['saved']:
|
if data['saved']:
|
||||||
attr = self.term.attr('saved', highlight)
|
attr = self.term.attr('saved')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, '[saved]', attr=attr)
|
self.term.add_line(win, '[saved]', attr=attr)
|
||||||
|
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ class SubredditPage(Page):
|
|||||||
self.content = page.selected_subreddit
|
self.content = page.selected_subreddit
|
||||||
self.nav = Navigator(self.content.get)
|
self.nav = Navigator(self.content.get)
|
||||||
|
|
||||||
def _draw_item(self, win, data, inverted, highlight):
|
def _draw_item(self, win, data, inverted):
|
||||||
|
|
||||||
n_rows, n_cols = win.getmaxyx()
|
n_rows, n_cols = win.getmaxyx()
|
||||||
n_cols -= 1 # Leave space for the cursor in the first column
|
n_cols -= 1 # Leave space for the cursor in the first column
|
||||||
@@ -264,75 +264,75 @@ class SubredditPage(Page):
|
|||||||
|
|
||||||
n_title = len(data['split_title'])
|
n_title = len(data['split_title'])
|
||||||
for row, text in enumerate(data['split_title'], start=offset):
|
for row, text in enumerate(data['split_title'], start=offset):
|
||||||
attr = self.term.attr('submission_title', highlight)
|
attr = self.term.attr('submission_title')
|
||||||
if row in valid_rows:
|
if row in valid_rows:
|
||||||
self.term.add_line(win, text, row, 1, attr)
|
self.term.add_line(win, text, row, 1, attr)
|
||||||
|
|
||||||
row = n_title + offset
|
row = n_title + offset
|
||||||
if row in valid_rows:
|
if row in valid_rows:
|
||||||
if data['url_full'] in self.config.history:
|
if data['url_full'] in self.config.history:
|
||||||
attr = self.term.attr('url_seen', highlight)
|
attr = self.term.attr('url_seen')
|
||||||
else:
|
else:
|
||||||
attr = self.term.attr('url', highlight)
|
attr = self.term.attr('url')
|
||||||
self.term.add_line(win, '{url}'.format(**data), row, 1, attr)
|
self.term.add_line(win, '{url}'.format(**data), row, 1, attr)
|
||||||
|
|
||||||
row = n_title + offset + 1
|
row = n_title + offset + 1
|
||||||
if row in valid_rows:
|
if row in valid_rows:
|
||||||
|
|
||||||
attr = self.term.attr('score', highlight)
|
attr = self.term.attr('score')
|
||||||
self.term.add_line(win, '{score}'.format(**data), row, 1, attr)
|
self.term.add_line(win, '{score}'.format(**data), row, 1, attr)
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
|
|
||||||
arrow, attr = self.term.get_arrow(data['likes'], highlight)
|
arrow, attr = self.term.get_arrow(data['likes'])
|
||||||
self.term.add_line(win, arrow, attr=attr)
|
self.term.add_line(win, arrow, attr=attr)
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
|
|
||||||
attr = self.term.attr('created', highlight)
|
attr = self.term.attr('created')
|
||||||
self.term.add_line(win, '{created}'.format(**data), attr=attr)
|
self.term.add_line(win, '{created}'.format(**data), attr=attr)
|
||||||
|
|
||||||
if data['comments'] is not None:
|
if data['comments'] is not None:
|
||||||
attr = self.term.attr('separator', highlight)
|
attr = self.term.attr('separator')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, '-', attr=attr)
|
self.term.add_line(win, '-', attr=attr)
|
||||||
|
|
||||||
attr = self.term.attr('comment_count', highlight)
|
attr = self.term.attr('comment_count')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, '{comments}'.format(**data), attr=attr)
|
self.term.add_line(win, '{comments}'.format(**data), attr=attr)
|
||||||
|
|
||||||
if data['saved']:
|
if data['saved']:
|
||||||
attr = self.term.attr('saved', highlight)
|
attr = self.term.attr('saved')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, '[saved]', attr=attr)
|
self.term.add_line(win, '[saved]', attr=attr)
|
||||||
|
|
||||||
if data['stickied']:
|
if data['stickied']:
|
||||||
attr = self.term.attr('stickied', highlight)
|
attr = self.term.attr('stickied')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, '[stickied]', attr=attr)
|
self.term.add_line(win, '[stickied]', attr=attr)
|
||||||
|
|
||||||
if data['gold']:
|
if data['gold']:
|
||||||
attr = self.term.attr('gold', highlight)
|
attr = self.term.attr('gold')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, self.term.guilded, attr=attr)
|
self.term.add_line(win, self.term.guilded, attr=attr)
|
||||||
|
|
||||||
if data['nsfw']:
|
if data['nsfw']:
|
||||||
attr = self.term.attr('nsfw', highlight)
|
attr = self.term.attr('nsfw')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, 'NSFW', attr=attr)
|
self.term.add_line(win, 'NSFW', attr=attr)
|
||||||
|
|
||||||
row = n_title + offset + 2
|
row = n_title + offset + 2
|
||||||
if row in valid_rows:
|
if row in valid_rows:
|
||||||
attr = self.term.attr('submission_author', highlight)
|
attr = self.term.attr('submission_author')
|
||||||
self.term.add_line(win, '{author}'.format(**data), row, 1, attr)
|
self.term.add_line(win, '{author}'.format(**data), row, 1, attr)
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
|
|
||||||
attr = self.term.attr('submission_subreddit', highlight)
|
attr = self.term.attr('submission_subreddit')
|
||||||
self.term.add_line(win, '/r/{subreddit}'.format(**data), attr=attr)
|
self.term.add_line(win, '/r/{subreddit}'.format(**data), attr=attr)
|
||||||
|
|
||||||
if data['flair']:
|
if data['flair']:
|
||||||
attr = self.term.attr('submission_flair', highlight)
|
attr = self.term.attr('submission_flair')
|
||||||
self.term.add_space(win)
|
self.term.add_space(win)
|
||||||
self.term.add_line(win, '{flair}'.format(**data), attr=attr)
|
self.term.add_line(win, '{flair}'.format(**data), attr=attr)
|
||||||
|
|
||||||
attr = self.term.attr('cursor', highlight)
|
attr = self.term.attr('cursor')
|
||||||
for y in range(n_rows):
|
for y in range(n_rows):
|
||||||
self.term.addch(win, y, 0, str(' '), attr)
|
self.term.addch(win, y, 0, str(' '), attr)
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class SubscriptionPage(Page):
|
|||||||
# Subscriptions can't be sorted, so disable showing the order menu
|
# Subscriptions can't be sorted, so disable showing the order menu
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _draw_item(self, win, data, inverted, highlight):
|
def _draw_item(self, win, data, inverted):
|
||||||
n_rows, n_cols = win.getmaxyx()
|
n_rows, n_cols = win.getmaxyx()
|
||||||
n_cols -= 1 # Leave space for the cursor in the first column
|
n_cols -= 1 # Leave space for the cursor in the first column
|
||||||
|
|
||||||
@@ -94,20 +94,20 @@ class SubscriptionPage(Page):
|
|||||||
row = offset
|
row = offset
|
||||||
if row in valid_rows:
|
if row in valid_rows:
|
||||||
if data['type'] == 'Multireddit':
|
if data['type'] == 'Multireddit':
|
||||||
attr = self.term.attr('multireddit_name', highlight)
|
attr = self.term.attr('multireddit_name')
|
||||||
else:
|
else:
|
||||||
attr = self.term.attr('subscription_name', highlight)
|
attr = self.term.attr('subscription_name')
|
||||||
self.term.add_line(win, '{name}'.format(**data), row, 1, attr)
|
self.term.add_line(win, '{name}'.format(**data), row, 1, attr)
|
||||||
|
|
||||||
row = offset + 1
|
row = offset + 1
|
||||||
for row, text in enumerate(data['split_title'], start=row):
|
for row, text in enumerate(data['split_title'], start=row):
|
||||||
if row in valid_rows:
|
if row in valid_rows:
|
||||||
if data['type'] == 'Multireddit':
|
if data['type'] == 'Multireddit':
|
||||||
attr = self.term.attr('multireddit_text', highlight)
|
attr = self.term.attr('multireddit_text')
|
||||||
else:
|
else:
|
||||||
attr = self.term.attr('subscription_text', highlight)
|
attr = self.term.attr('subscription_text')
|
||||||
self.term.add_line(win, text, row, 1, attr)
|
self.term.add_line(win, text, row, 1, attr)
|
||||||
|
|
||||||
attr = self.term.attr('cursor', highlight)
|
attr = self.term.attr('cursor')
|
||||||
for y in range(n_rows):
|
for y in range(n_rows):
|
||||||
self.term.addch(win, y, 0, str(' '), attr)
|
self.term.addch(win, y, 0, str(' '), attr)
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ class Terminal(object):
|
|||||||
finally:
|
finally:
|
||||||
self.stdscr.nodelay(0)
|
self.stdscr.nodelay(0)
|
||||||
|
|
||||||
def get_arrow(self, likes, highlight=False):
|
def get_arrow(self, likes):
|
||||||
"""
|
"""
|
||||||
Curses does define constants for symbols (e.g. curses.ACS_BULLET).
|
Curses does define constants for symbols (e.g. curses.ACS_BULLET).
|
||||||
However, they rely on using the curses.addch() function, which has been
|
However, they rely on using the curses.addch() function, which has been
|
||||||
@@ -193,11 +193,11 @@ class Terminal(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if likes is None:
|
if likes is None:
|
||||||
return self.neutral_arrow, self.attr('neutral_vote', highlight)
|
return self.neutral_arrow, self.attr('neutral_vote')
|
||||||
elif likes:
|
elif likes:
|
||||||
return self.up_arrow, self.attr('upvote', highlight)
|
return self.up_arrow, self.attr('upvote')
|
||||||
else:
|
else:
|
||||||
return self.down_arrow, self.attr('downvote', highlight)
|
return self.down_arrow, self.attr('downvote')
|
||||||
|
|
||||||
def clean(self, string, n_cols=None):
|
def clean(self, string, n_cols=None):
|
||||||
"""
|
"""
|
||||||
@@ -282,7 +282,8 @@ class Terminal(object):
|
|||||||
|
|
||||||
row, col = window.getyx()
|
row, col = window.getyx()
|
||||||
_, max_cols = window.getmaxyx()
|
_, max_cols = window.getmaxyx()
|
||||||
if max_cols - col - 1 <= 0:
|
n_cols = max_cols - col - 1
|
||||||
|
if n_cols <= 0:
|
||||||
# Trying to draw outside of the screen bounds
|
# Trying to draw outside of the screen bounds
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -300,6 +301,8 @@ class Terminal(object):
|
|||||||
notification window
|
notification window
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
assert style in ('info', 'warning', 'error', 'success')
|
||||||
|
|
||||||
if isinstance(message, six.string_types):
|
if isinstance(message, six.string_types):
|
||||||
message = message.splitlines()
|
message = message.splitlines()
|
||||||
|
|
||||||
@@ -396,7 +399,7 @@ class Terminal(object):
|
|||||||
_logger.warning(stderr)
|
_logger.warning(stderr)
|
||||||
self.show_notification(
|
self.show_notification(
|
||||||
'Program exited with status={0}\n{1}'.format(
|
'Program exited with status={0}\n{1}'.format(
|
||||||
code, stderr.strip()))
|
code, stderr.strip()), style='error')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Non-blocking, open a background process
|
# Non-blocking, open a background process
|
||||||
@@ -821,17 +824,18 @@ class Terminal(object):
|
|||||||
else:
|
else:
|
||||||
self.stdscr.clearok(True)
|
self.stdscr.clearok(True)
|
||||||
|
|
||||||
def attr(self, element, highlight=False):
|
def attr(self, element):
|
||||||
"""
|
"""
|
||||||
Shortcut for fetching the color + attribute code for an element.
|
Shortcut for fetching the color + attribute code for an element.
|
||||||
"""
|
"""
|
||||||
return self.theme.get(element, highlight=highlight)
|
|
||||||
|
return self.theme.get(element)
|
||||||
|
|
||||||
def set_theme(self, theme=None):
|
def set_theme(self, theme=None):
|
||||||
"""
|
"""
|
||||||
Check that the terminal supports the provided theme, and applies
|
Check that the terminal supports the provided theme, and applies
|
||||||
the theme to the terminal if possible.
|
the theme to the terminal if possible.
|
||||||
|
|
||||||
If the terminal doesn't support the theme, this falls back to the
|
If the terminal doesn't support the theme, this falls back to the
|
||||||
default theme. The default theme only requires 8 colors so it
|
default theme. The default theme only requires 8 colors so it
|
||||||
should be compatible with any terminal that supports basic colors.
|
should be compatible with any terminal that supports basic colors.
|
||||||
@@ -862,4 +866,4 @@ class Terminal(object):
|
|||||||
# Apply the default color to the whole screen
|
# Apply the default color to the whole screen
|
||||||
self.stdscr.bkgd(str(' '), theme.get('@normal'))
|
self.stdscr.bkgd(str(' '), theme.get('@normal'))
|
||||||
|
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
29
rtv/theme.py
29
rtv/theme.py
@@ -1,8 +1,9 @@
|
|||||||
|
import os
|
||||||
import codecs
|
import codecs
|
||||||
import configparser
|
|
||||||
import curses
|
import curses
|
||||||
import logging
|
import logging
|
||||||
import os
|
import configparser
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
from .config import THEMES, DEFAULT_THEMES
|
from .config import THEMES, DEFAULT_THEMES
|
||||||
from .exceptions import ConfigError
|
from .exceptions import ConfigError
|
||||||
@@ -116,6 +117,7 @@ class Theme(object):
|
|||||||
self.monochrome = monochrome
|
self.monochrome = monochrome
|
||||||
self._color_pair_map = None
|
self._color_pair_map = None
|
||||||
self._attribute_map = None
|
self._attribute_map = None
|
||||||
|
self._modifier = None
|
||||||
|
|
||||||
self.required_color_pairs = 0
|
self.required_color_pairs = 0
|
||||||
self.required_colors = 0
|
self.required_colors = 0
|
||||||
@@ -206,7 +208,7 @@ class Theme(object):
|
|||||||
|
|
||||||
self._attribute_map[element] = attrs
|
self._attribute_map[element] = attrs
|
||||||
|
|
||||||
def get(self, val, highlight=False):
|
def get(self, element, modifier=None):
|
||||||
"""
|
"""
|
||||||
Returns the curses attribute code for the given element.
|
Returns the curses attribute code for the given element.
|
||||||
"""
|
"""
|
||||||
@@ -214,10 +216,25 @@ class Theme(object):
|
|||||||
raise RuntimeError('Attempted to access theme attribute before '
|
raise RuntimeError('Attempted to access theme attribute before '
|
||||||
'calling initialize_curses_theme()')
|
'calling initialize_curses_theme()')
|
||||||
|
|
||||||
if highlight:
|
modifier = modifier or self._modifier
|
||||||
val = val + '.highlight'
|
if modifier:
|
||||||
|
modified_element = '{0}.{1}'.format(element, modifier)
|
||||||
|
if modified_element in self._elements:
|
||||||
|
return self._elements[modified_element]
|
||||||
|
|
||||||
return self._attribute_map[val]
|
return self._attribute_map[element]
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def set_modifier(self, modifier=None):
|
||||||
|
|
||||||
|
# This case is undefined if the context manager is nested
|
||||||
|
assert self._modifier is None
|
||||||
|
|
||||||
|
self._modifier = modifier
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
self._modifier = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def list_themes(cls, path=THEMES):
|
def list_themes(cls, path=THEMES):
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ class MockStdscr(mock.MagicMock):
|
|||||||
def getyx(self):
|
def getyx(self):
|
||||||
return self.y, self.x
|
return self.y, self.x
|
||||||
|
|
||||||
|
def getbegyx(self):
|
||||||
|
return 0, 0
|
||||||
|
|
||||||
def getmaxyx(self):
|
def getmaxyx(self):
|
||||||
return self.nlines, self.ncols
|
return self.nlines, self.ncols
|
||||||
|
|
||||||
@@ -154,12 +157,14 @@ def stdscr():
|
|||||||
patch('curses.curs_set'), \
|
patch('curses.curs_set'), \
|
||||||
patch('curses.init_pair'), \
|
patch('curses.init_pair'), \
|
||||||
patch('curses.color_pair'), \
|
patch('curses.color_pair'), \
|
||||||
|
patch('curses.has_colors'), \
|
||||||
patch('curses.start_color'), \
|
patch('curses.start_color'), \
|
||||||
patch('curses.use_default_colors'):
|
patch('curses.use_default_colors'):
|
||||||
out = MockStdscr(nlines=40, ncols=80, x=0, y=0)
|
out = MockStdscr(nlines=40, ncols=80, x=0, y=0)
|
||||||
curses.initscr.return_value = out
|
curses.initscr.return_value = out
|
||||||
curses.newwin.side_effect = lambda *args: out.derwin(*args)
|
curses.newwin.side_effect = lambda *args: out.derwin(*args)
|
||||||
curses.color_pair.return_value = 23
|
curses.color_pair.return_value = 23
|
||||||
|
curses.has_colors.return_value = True
|
||||||
curses.ACS_VLINE = 0
|
curses.ACS_VLINE = 0
|
||||||
yield out
|
yield out
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import requests
|
|||||||
from six.moves import reload_module
|
from six.moves import reload_module
|
||||||
|
|
||||||
from rtv import exceptions
|
from rtv import exceptions
|
||||||
from rtv.objects import Color, Controller, Navigator, Command, KeyMap, \
|
from rtv.objects import Controller, Navigator, Command, KeyMap, \
|
||||||
curses_session, patch_webbrowser
|
curses_session, patch_webbrowser
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -189,21 +189,6 @@ def test_objects_load_screen_nested_complex(terminal, stdscr, use_ascii):
|
|||||||
stdscr.subwin.addstr.assert_called_once_with(1, 1, error_message)
|
stdscr.subwin.addstr.assert_called_once_with(1, 1, error_message)
|
||||||
|
|
||||||
|
|
||||||
def test_objects_color(stdscr):
|
|
||||||
|
|
||||||
colors = ['RED', 'GREEN', 'YELLOW', 'BLUE', 'MAGENTA', 'CYAN', 'WHITE']
|
|
||||||
|
|
||||||
# Check that all colors start with the default value
|
|
||||||
for color in colors:
|
|
||||||
assert getattr(Color, color) == curses.A_NORMAL
|
|
||||||
|
|
||||||
Color.init()
|
|
||||||
|
|
||||||
# Check that all colors are populated
|
|
||||||
for color in colors:
|
|
||||||
assert getattr(Color, color) == 23
|
|
||||||
|
|
||||||
|
|
||||||
def test_objects_curses_session(stdscr):
|
def test_objects_curses_session(stdscr):
|
||||||
|
|
||||||
# Normal setup and cleanup
|
# Normal setup and cleanup
|
||||||
|
|||||||
@@ -79,15 +79,16 @@ def test_submission_page_construct(reddit, terminal, config, oauth):
|
|||||||
# Comment
|
# Comment
|
||||||
comment_data = page.content.get(0)
|
comment_data = page.content.get(0)
|
||||||
text = comment_data['split_body'][0].encode('utf-8')
|
text = comment_data['split_body'][0].encode('utf-8')
|
||||||
window.subwin.addstr.assert_any_call(1, 1, text)
|
window.subwin.addstr.assert_any_call(1, 1, text, curses.A_NORMAL)
|
||||||
|
|
||||||
# More Comments
|
# More Comments
|
||||||
comment_data = page.content.get(1)
|
comment_data = page.content.get(1)
|
||||||
text = comment_data['body'].encode('utf-8')
|
text = comment_data['body'].encode('utf-8')
|
||||||
window.subwin.addstr.assert_any_call(0, 1, text)
|
window.subwin.addstr.assert_any_call(0, 1, text, curses.A_NORMAL)
|
||||||
|
|
||||||
# Cursor should not be drawn when the page is first opened
|
# Cursor should not be drawn when the page is first opened
|
||||||
assert not window.subwin.chgat.called
|
assert not any(args[0][3] == curses.A_REVERSE
|
||||||
|
for args in window.subwin.addch.call_args_list)
|
||||||
|
|
||||||
# Reload with a smaller terminal window
|
# Reload with a smaller terminal window
|
||||||
terminal.stdscr.ncols = 20
|
terminal.stdscr.ncols = 20
|
||||||
@@ -264,7 +265,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(6, 1, text)
|
window.subwin.addstr.assert_any_call(6, 1, text, curses.A_NORMAL)
|
||||||
|
|
||||||
|
|
||||||
def test_submission_vote(submission_page, refresh_token):
|
def test_submission_vote(submission_page, refresh_token):
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import curses
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from rtv import __version__
|
from rtv import __version__
|
||||||
@@ -38,7 +40,7 @@ def test_subreddit_page_construct(reddit, terminal, config, oauth):
|
|||||||
window.subwin.addstr.assert_any_call(0, 1, text, 2097152)
|
window.subwin.addstr.assert_any_call(0, 1, text, 2097152)
|
||||||
|
|
||||||
# Cursor should have been drawn
|
# Cursor should have been drawn
|
||||||
assert window.subwin.chgat.called
|
window.subwin.addch.assert_any_call(0, 0, ' ', curses.A_REVERSE)
|
||||||
|
|
||||||
# Reload with a smaller terminal window
|
# Reload with a smaller terminal window
|
||||||
terminal.stdscr.ncols = 20
|
terminal.stdscr.ncols = 20
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ def test_subscription_page_construct(reddit, terminal, config, oauth,
|
|||||||
window.addstr.assert_any_call(0, 0, menu)
|
window.addstr.assert_any_call(0, 0, menu)
|
||||||
|
|
||||||
# Cursor - 2 lines
|
# Cursor - 2 lines
|
||||||
window.subwin.chgat.assert_any_call(0, 0, 1, 262144)
|
window.subwin.addch.assert_any_call(0, 0, ' ', 262144)
|
||||||
window.subwin.chgat.assert_any_call(1, 0, 1, 262144)
|
window.subwin.addch.assert_any_call(1, 0, ' ', 262144)
|
||||||
|
|
||||||
# Reload with a smaller terminal window
|
# Reload with a smaller terminal window
|
||||||
terminal.stdscr.ncols = 20
|
terminal.stdscr.ncols = 20
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import codecs
|
|||||||
import six
|
import six
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from rtv.theme import Theme
|
||||||
from rtv.docs import HELP, COMMENT_EDIT_FILE
|
from rtv.docs import HELP, COMMENT_EDIT_FILE
|
||||||
from rtv.exceptions import TemporaryFileError, BrowserError
|
from rtv.exceptions import TemporaryFileError, BrowserError
|
||||||
|
|
||||||
@@ -20,14 +21,10 @@ except ImportError:
|
|||||||
|
|
||||||
def test_terminal_properties(terminal, config):
|
def test_terminal_properties(terminal, config):
|
||||||
|
|
||||||
assert len(terminal.up_arrow) == 2
|
assert isinstance(terminal.up_arrow, six.text_type)
|
||||||
assert isinstance(terminal.up_arrow[0], six.text_type)
|
assert isinstance(terminal.down_arrow, six.text_type)
|
||||||
assert len(terminal.down_arrow) == 2
|
assert isinstance(terminal.neutral_arrow, six.text_type)
|
||||||
assert isinstance(terminal.down_arrow[0], six.text_type)
|
assert isinstance(terminal.guilded, six.text_type)
|
||||||
assert len(terminal.neutral_arrow) == 2
|
|
||||||
assert isinstance(terminal.neutral_arrow[0], six.text_type)
|
|
||||||
assert len(terminal.guilded) == 2
|
|
||||||
assert isinstance(terminal.guilded[0], six.text_type)
|
|
||||||
|
|
||||||
terminal._display = None
|
terminal._display = None
|
||||||
with mock.patch('rtv.terminal.sys') as sys, \
|
with mock.patch('rtv.terminal.sys') as sys, \
|
||||||
@@ -60,6 +57,8 @@ def test_terminal_properties(terminal, config):
|
|||||||
assert terminal.MIN_HEIGHT is not None
|
assert terminal.MIN_HEIGHT is not None
|
||||||
assert terminal.MIN_WIDTH is not None
|
assert terminal.MIN_WIDTH is not None
|
||||||
|
|
||||||
|
assert terminal.theme is not None
|
||||||
|
|
||||||
|
|
||||||
def test_terminal_functions(terminal):
|
def test_terminal_functions(terminal):
|
||||||
|
|
||||||
@@ -558,3 +557,42 @@ def test_strip_textpad(terminal):
|
|||||||
text = 'alpha bravo\ncharlie \ndelta \n echo \n\nfoxtrot\n\n\n'
|
text = 'alpha bravo\ncharlie \ndelta \n echo \n\nfoxtrot\n\n\n'
|
||||||
assert terminal.strip_textpad(text) == (
|
assert terminal.strip_textpad(text) == (
|
||||||
'alpha bravocharlie delta\n echo\n\nfoxtrot')
|
'alpha bravocharlie delta\n echo\n\nfoxtrot')
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_space(terminal, stdscr):
|
||||||
|
|
||||||
|
stdscr.x, stdscr.y = 10, 20
|
||||||
|
terminal.add_space(stdscr)
|
||||||
|
stdscr.addstr.assert_called_with(20, 10, ' ')
|
||||||
|
|
||||||
|
# Not enough room to add a space
|
||||||
|
stdscr.reset_mock()
|
||||||
|
stdscr.x = 10
|
||||||
|
stdscr.ncols = 11
|
||||||
|
terminal.add_space(stdscr)
|
||||||
|
assert not stdscr.addstr.called
|
||||||
|
|
||||||
|
|
||||||
|
def test_attr(terminal):
|
||||||
|
|
||||||
|
assert terminal.attr('cursor') == 0
|
||||||
|
assert terminal.attr('cursor.selected') == curses.A_REVERSE
|
||||||
|
assert terminal.attr('neutral_vote') == curses.A_BOLD
|
||||||
|
|
||||||
|
with terminal.theme.set_modifier('selected'):
|
||||||
|
assert terminal.attr('cursor') == curses.A_REVERSE
|
||||||
|
assert terminal.attr('neutral_vote') == curses.A_BOLD
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_theme(terminal, stdscr):
|
||||||
|
|
||||||
|
stdscr.reset_mock()
|
||||||
|
terminal.set_theme()
|
||||||
|
assert not terminal.theme.monochrome
|
||||||
|
stdscr.bkgd.assert_called_once_with(' ', 0)
|
||||||
|
|
||||||
|
stdscr.reset_mock()
|
||||||
|
theme = Theme(monochrome=True)
|
||||||
|
terminal.set_theme(theme=theme)
|
||||||
|
assert terminal.theme.monochrome
|
||||||
|
stdscr.bkgd.assert_called_once_with(' ', 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user