Switching to more vim-inspired theme attributes

This commit is contained in:
Michael Lazar
2017-09-19 02:10:37 -04:00
parent b941dc22af
commit d8213f2271
13 changed files with 385 additions and 333 deletions

View File

@@ -178,8 +178,10 @@ def main():
term = Terminal(stdscr, config) term = Terminal(stdscr, config)
if config['monochrome']: if config['monochrome']:
_logger.info('Using monochrome theme')
theme = Theme(use_color=False) theme = Theme(use_color=False)
elif config['theme']: elif config['theme']:
_logger.info('Loading theme: %s', config['theme'])
theme = Theme.from_name(config['theme']) theme = Theme.from_name(config['theme'])
else: else:
# Set to None to let the terminal figure out which default # Set to None to let the terminal figure out which default

View File

@@ -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', style='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', style='error') self.term.show_notification('Denied access', style='Error')
return return
elif self.params['error']: elif self.params['error']:
self.term.show_notification('Authentication error', style='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', style='error') self.term.show_notification('UUID mismatch', style='Error')
return return
with self.term.loader('Logging in'): with self.term.loader('Logging in'):

View File

@@ -231,7 +231,7 @@ class LoadScreen(object):
# Some exceptions we want to swallow and display a notification # Some exceptions we want to swallow and display a notification
if isinstance(e, e_type): if isinstance(e, e_type):
msg = message.format(e) msg = message.format(e)
self._terminal.show_notification(msg, style='error') self._terminal.show_notification(msg, style='Error')
return True return True
def animate(self, delay, interval, message, trail): def animate(self, delay, interval, message, trail):
@@ -260,7 +260,7 @@ class LoadScreen(object):
s_row = (n_rows - 3) // 2 + v_offset s_row = (n_rows - 3) // 2 + v_offset
s_col = (n_cols - message_len - 1) // 2 + h_offset s_col = (n_cols - message_len - 1) // 2 + h_offset
window = curses.newwin(3, message_len + 2, s_row, s_col) window = curses.newwin(3, message_len + 2, s_row, s_col)
window.bkgd(str(' '), self._terminal.attr('notice_loading')) window.bkgd(str(' '), self._terminal.attr('NoticeLoading'))
# Animate the loading prompt until the stopping condition is triggered # Animate the loading prompt until the stopping condition is triggered
# when the context manager exits. # when the context manager exits.

View File

@@ -367,7 +367,7 @@ class Page(object):
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()
# curses.bkgd expects bytes in py2 and unicode in py3 # curses.bkgd expects bytes in py2 and unicode in py3
window.bkgd(str(' '), self.term.attr('title_bar')) window.bkgd(str(' '), self.term.attr('PageTitle'))
sub_name = self.content.name sub_name = self.content.name
sub_name = sub_name.replace('/r/front', 'Front Page') sub_name = sub_name.replace('/r/front', 'Front Page')
@@ -420,7 +420,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(1, n_cols, self._row, 0) window = self.term.stdscr.derwin(1, n_cols, self._row, 0)
window.erase() window.erase()
window.bkgd(str(' '), self.term.attr('order_bar')) window.bkgd(str(' '), self.term.attr('PageOrder'))
banner = docs.BANNER_SEARCH if self.content.query else docs.BANNER banner = docs.BANNER_SEARCH if self.content.query else docs.BANNER
items = banner.strip().split(' ') items = banner.strip().split(' ')
@@ -432,7 +432,7 @@ 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
attr = self.term.theme.get('order_bar', modifier='highlight') attr = self.term.attr('PageOrderHighlight')
window.chgat(0, col, 3, attr) window.chgat(0, col, 3, attr)
self._row += 1 self._row += 1
@@ -499,17 +499,17 @@ class Page(object):
# pushed out of bounds # pushed out of bounds
self.nav.cursor_index = len(self._subwindows) - 1 self.nav.cursor_index = len(self._subwindows) - 1
# TODO: Don't highlight the submission box
# 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):
if index == self.nav.cursor_index: if index == self.nav.cursor_index:
win.bkgd(str(' '), self.term.attr('@highlight')) win.bkgd(str(' '), self.term.attr('Selected'))
# This lets the theme know to invert the cursor color and with self.term.theme.turn_on_selected():
# apply any other special highlighting effects to the window
with self.term.theme.set_modifier('highlight'):
self._draw_item(win, data, inverted) self._draw_item(win, data, inverted)
else: else:
win.bkgd(str(' '), self.term.attr('@normal')) win.bkgd(str(' '), self.term.attr('Normal'))
self._draw_item(win, data, inverted) self._draw_item(win, data, inverted)
self._row += win_n_rows self._row += win_n_rows
@@ -519,7 +519,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(1, n_cols, self._row, 0) window = self.term.stdscr.derwin(1, n_cols, self._row, 0)
window.erase() window.erase()
window.bkgd(str(' '), self.term.attr('help_bar')) window.bkgd(str(' '), self.term.attr('Help'))
text = self.FOOTER.strip() text = self.FOOTER.strip()
self.term.add_line(window, text, 0, 0) self.term.add_line(window, text, 0, 0)

View File

@@ -315,15 +315,15 @@ 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') attr = self.term.attr('CommentAuthorSelf')
text = '{author} [S]'.format(**data) text = '{author} [S]'.format(**data)
else: else:
attr = self.term.attr('comment_author') attr = self.term.attr('CommentAuthor')
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') attr = self.term.attr('UserFlair')
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)
@@ -331,38 +331,38 @@ class SubmissionPage(Page):
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') 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') 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') 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') 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') 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') attr = self.term.attr('CommentText')
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 # curses.vline() doesn't support custom colors so need to build the
# build it one segment at a time. # cursor bar on the left of the comment one character at a time
index = data['level'] % len(self.term.theme.BAR_LEVELS) index = data['level'] % len(self.term.theme.CURSOR_BARS)
attr = self.term.attr(self.term.theme.BAR_LEVELS[index]) attr = self.term.attr(self.term.theme.CURSOR_BARS[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)
@@ -371,15 +371,15 @@ class SubmissionPage(Page):
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') attr = self.term.attr('HiddenCommentText')
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') attr = self.term.attr('HiddenCommentExpand')
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.CURSOR_BARS)
attr = self.term.attr(self.term.theme.BAR_LEVELS[index]) attr = self.term.attr(self.term.theme.CURSOR_BARS[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): def _draw_submission(self, win, data):
@@ -387,32 +387,32 @@ class SubmissionPage(Page):
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') attr = self.term.attr('SubmissionTitle')
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') attr = self.term.attr('SubmissionAuthor')
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') attr = self.term.attr('SubmissionFlair')
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') 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') attr = self.term.attr('SubmissionSubreddit')
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') attr = self.term.attr('LinkSeen')
else: else:
attr = self.term.attr('url') attr = self.term.attr('Link')
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
@@ -424,34 +424,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') attr = self.term.attr('SubmissionText')
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') 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']) 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') attr = self.term.attr('CommentCount')
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') 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') 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') 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)

View File

@@ -304,22 +304,22 @@ 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') attr = self.term.attr('SubmissionTitle')
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') attr = self.term.attr('LinkSeen')
else: else:
attr = self.term.attr('url') attr = self.term.attr('Link')
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') 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)
@@ -327,52 +327,52 @@ class SubredditPage(Page):
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') 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') 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') attr = self.term.attr('CommentCount')
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') 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') 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') 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') 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') attr = self.term.attr('SubmissionAuthor')
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') attr = self.term.attr('SubmissionSubreddit')
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') attr = self.term.attr('SubmissionFlair')
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') attr = self.term.attr('CursorBlock')
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)

View File

@@ -93,20 +93,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') attr = self.term.attr('MultiredditName')
else: else:
attr = self.term.attr('subscription_name') attr = self.term.attr('SubscriptionName')
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') attr = self.term.attr('MultiredditText')
else: else:
attr = self.term.attr('subscription_text') attr = self.term.attr('SubscriptionText')
self.term.add_line(win, text, row, 1, attr) self.term.add_line(win, text, row, 1, attr)
attr = self.term.attr('cursor') attr = self.term.attr('CursorBlock')
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)

View File

@@ -191,11 +191,11 @@ class Terminal(object):
""" """
if likes is None: if likes is None:
return self.neutral_arrow, self.attr('neutral_vote') return self.neutral_arrow, self.attr('NeutralVote')
elif likes: elif likes:
return self.up_arrow, self.attr('upvote') return self.up_arrow, self.attr('Upvote')
else: else:
return self.down_arrow, self.attr('downvote') return self.down_arrow, self.attr('Downvote')
def clean(self, string, n_cols=None): def clean(self, string, n_cols=None):
""" """
@@ -287,7 +287,7 @@ class Terminal(object):
window.addstr(row, col, ' ') window.addstr(row, col, ' ')
def show_notification(self, message, timeout=None, style='info'): def show_notification(self, message, timeout=None, style='Info'):
""" """
Overlay a message box on the center of the screen and wait for input. Overlay a message box on the center of the screen and wait for input.
@@ -299,7 +299,7 @@ class Terminal(object):
notification window notification window
""" """
assert style in ('info', 'warning', 'error', 'success') 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()
@@ -319,7 +319,7 @@ class Terminal(object):
s_col = (n_cols - box_width) // 2 + h_offset s_col = (n_cols - box_width) // 2 + h_offset
window = curses.newwin(box_height, box_width, s_row, s_col) window = curses.newwin(box_height, box_width, s_row, s_col)
window.bkgd(str(' '), self.attr('notice_{0}'.format(style))) window.bkgd(str(' '), self.attr('Notice{0}'.format(style)))
window.erase() window.erase()
window.border() window.border()
@@ -708,7 +708,7 @@ class Terminal(object):
n_rows, n_cols = self.stdscr.getmaxyx() n_rows, n_cols = self.stdscr.getmaxyx()
v_offset, h_offset = self.stdscr.getbegyx() v_offset, h_offset = self.stdscr.getbegyx()
ch, attr = str(' '), self.attr('prompt') ch, attr = str(' '), self.attr('Prompt')
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,
@@ -864,8 +864,7 @@ class Terminal(object):
theme = Theme() theme = Theme()
theme.bind_curses() theme.bind_curses()
self.theme = theme
# 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(' '), self.attr('Normal'))
self.theme = theme

View File

@@ -47,66 +47,120 @@ class Theme(object):
'white': 15, 'white': 15,
} }
# Add keywords for the 256 ansi color codes
for i in range(256): for i in range(256):
COLOR_CODES['ansi_{0}'.format(i)] = i COLOR_CODES['ansi_{0}'.format(i)] = i
# TODO: Do another pass through these names
# For compatibility with as many terminals as possible, the default theme # For compatibility with as many terminals as possible, the default theme
# can only use the 8 basic colors with the default color as the background # can only use the 8 basic colors with the default color as the background
DEFAULT_THEME = { DEFAULT_THEME = {
'@normal': (-1, -1, curses.A_NORMAL), 'Normal': (None, None, None),
'@highlight': (-1, -1, curses.A_NORMAL), 'Selected': (None, None, None),
'SelectedCursor': (None, None, curses.A_REVERSE),
'bar_level_1': (curses.COLOR_MAGENTA, None, curses.A_NORMAL), 'PageTitle': (curses.COLOR_CYAN, None, curses.A_BOLD | curses.A_REVERSE),
'bar_level_1.highlight': (curses.COLOR_MAGENTA, None, curses.A_REVERSE), 'PageOrder': (curses.COLOR_YELLOW, None, curses.A_BOLD),
'bar_level_2': (curses.COLOR_CYAN, None, curses.A_NORMAL), 'PageOrderHighlight': (curses.COLOR_YELLOW, None, curses.A_BOLD | curses.A_REVERSE),
'bar_level_2.highlight': (curses.COLOR_CYAN, None, curses.A_REVERSE), 'Help': (curses.COLOR_CYAN, None, curses.A_BOLD | curses.A_REVERSE),
'bar_level_3': (curses.COLOR_GREEN, None, curses.A_NORMAL), 'Prompt': (curses.COLOR_CYAN, None, curses.A_BOLD | curses.A_REVERSE),
'bar_level_3.highlight': (curses.COLOR_GREEN, None, curses.A_REVERSE), 'NoticeInfo': (None, None, curses.A_BOLD),
'bar_level_4': (curses.COLOR_YELLOW, None, curses.A_NORMAL), 'NoticeLoading': (None, None, curses.A_BOLD),
'bar_level_4.highlight': (curses.COLOR_YELLOW, None, curses.A_REVERSE), 'NoticeError': (None, None, curses.A_BOLD),
'comment_author': (curses.COLOR_BLUE, None, curses.A_BOLD), 'NoticeSuccess': (None, None, curses.A_BOLD),
'comment_author_self': (curses.COLOR_GREEN, None, curses.A_BOLD),
'comment_count': (None, None, curses.A_NORMAL), 'CursorBlock': (None, None, None),
'comment_text': (None, None, curses.A_NORMAL), 'CursorBar1': (curses.COLOR_MAGENTA, None, None),
'created': (None, None, curses.A_NORMAL), 'CursorBar2': (curses.COLOR_CYAN, None, None),
'cursor': (None, None, curses.A_NORMAL), 'CursorBar3': (curses.COLOR_GREEN, None, None),
'cursor.highlight': (None, None, curses.A_REVERSE), 'CursorBar4': (curses.COLOR_YELLOW, None, None),
'downvote': (curses.COLOR_RED, None, curses.A_BOLD),
'gold': (curses.COLOR_YELLOW, None, curses.A_BOLD), 'CommentAuthor': (curses.COLOR_BLUE, None, curses.A_BOLD),
'help_bar': (curses.COLOR_CYAN, None, curses.A_BOLD | curses.A_REVERSE), 'CommentAuthorSelf': (curses.COLOR_GREEN, None, curses.A_BOLD),
'hidden_comment_expand': (None, None, curses.A_BOLD), 'CommentCount': (None, None, None),
'hidden_comment_text': (None, None, curses.A_NORMAL), 'CommentText': (None, None, None),
'multireddit_name': (curses.COLOR_YELLOW, None, curses.A_BOLD), 'Created': (None, None, None),
'multireddit_text': (None, None, curses.A_NORMAL), 'Downvote': (curses.COLOR_RED, None, curses.A_BOLD),
'neutral_vote': (None, None, curses.A_BOLD), 'Gold': (curses.COLOR_YELLOW, None, curses.A_BOLD),
'notice_info': (None, None, curses.A_NORMAL), 'HiddenCommentExpand': (None, None, curses.A_BOLD),
'notice_loading': (None, None, curses.A_NORMAL), 'HiddenCommentText': (None, None, None),
'notice_error': (curses.COLOR_RED, None, curses.A_NORMAL), 'MultiredditName': (curses.COLOR_YELLOW, None, curses.A_BOLD),
'notice_success': (curses.COLOR_GREEN, None, curses.A_NORMAL), 'MultiredditText': (None, None, None),
'nsfw': (curses.COLOR_RED, None, curses.A_BOLD), 'NeutralVote': (None, None, curses.A_BOLD),
'order_bar': (curses.COLOR_YELLOW, None, curses.A_BOLD), 'NSFW': (curses.COLOR_RED, None, curses.A_BOLD),
'order_bar.highlight': (curses.COLOR_YELLOW, None, curses.A_BOLD | curses.A_REVERSE), 'Saved': (curses.COLOR_GREEN, None, None),
'prompt': (curses.COLOR_CYAN, None, curses.A_BOLD | curses.A_REVERSE), 'Score': (None, None, None),
'saved': (curses.COLOR_GREEN, None, curses.A_NORMAL), 'Separator': (None, None, curses.A_BOLD),
'score': (None, None, curses.A_NORMAL), 'Stickied': (curses.COLOR_GREEN, None, None),
'separator': (None, None, curses.A_BOLD), 'SubscriptionName': (curses.COLOR_YELLOW, None, curses.A_BOLD),
'stickied': (curses.COLOR_GREEN, None, curses.A_NORMAL), 'SubscriptionText': (None, None, None),
'subscription_name': (curses.COLOR_YELLOW, None, curses.A_BOLD), 'SubmissionAuthor': (curses.COLOR_GREEN, None, None),
'subscription_text': (None, None, curses.A_NORMAL), 'SubmissionFlair': (curses.COLOR_RED, None, None),
'submission_author': (curses.COLOR_GREEN, None, curses.A_NORMAL), 'SubmissionSubreddit': (curses.COLOR_YELLOW, None, None),
'submission_flair': (curses.COLOR_RED, None, curses.A_NORMAL), 'SubmissionText': (None, None, None),
'submission_subreddit': (curses.COLOR_YELLOW, None, curses.A_NORMAL), 'SubmissionTitle': (None, None, curses.A_BOLD),
'submission_text': (None, None, curses.A_NORMAL), 'Upvote': (curses.COLOR_GREEN, None, curses.A_BOLD),
'submission_title': (None, None, curses.A_BOLD), 'Link': (curses.COLOR_BLUE, None, curses.A_UNDERLINE),
'title_bar': (curses.COLOR_CYAN, None, curses.A_BOLD | curses.A_REVERSE), 'LinkSeen': (curses.COLOR_MAGENTA, None, curses.A_UNDERLINE),
'upvote': (curses.COLOR_GREEN, None, curses.A_BOLD), 'UserFlair': (curses.COLOR_YELLOW, None, curses.A_BOLD)
'url': (curses.COLOR_BLUE, None, curses.A_UNDERLINE),
'url_seen': (curses.COLOR_MAGENTA, None, curses.A_UNDERLINE),
'user_flair': (curses.COLOR_YELLOW, None, curses.A_BOLD)
} }
BAR_LEVELS = ['bar_level_1', 'bar_level_2', 'bar_level_3', 'bar_level_4'] # List of elements that might be highlighted by the "Selected" row
SELECTED_ELEMENTS = [
'CommentAuthor',
'CommentAuthorSelf',
'CommentCount',
'CommentText',
'Created',
'Downvote',
'Gold',
'HiddenCommentExpand',
'HiddenCommentText',
'MultiredditName',
'MultiredditText',
'NeutralVote',
'NSFW',
'Saved',
'Score',
'Separator',
'Stickied',
'SubscriptionName',
'SubscriptionText',
'SubmissionAuthor',
'SubmissionFlair',
'SubmissionSubreddit',
'SubmissionText',
'SubmissionTitle',
'Upvote',
'Link',
'LinkSeen',
'UserFlair'
]
# List of elements that might be highlighted by the "SelectedCursor" row
SELECTED_CURSOR_ELEMENTS = [
'CursorBlock',
'CursorBar1',
'CursorBar2',
'CursorBar3',
'CursorBar4'
]
# List of page elements that cannot be selected
PAGE_ELEMENTS = [
'PageOrder',
'PageOrderHighlight',
'PageTitle',
'Help',
'Prompt',
'NoticeInfo',
'NoticeLoading',
'NoticeError',
'NoticeSuccess',
]
# The SubmissionPage uses this to determine which color bar to use
CURSOR_BARS = ['CursorBar1', 'CursorBar2', 'CursorBar3', 'CursorBar4']
def __init__(self, name=None, source=None, elements=None, use_color=True): def __init__(self, name=None, source=None, elements=None, use_color=True):
""" """
@@ -131,9 +185,10 @@ class Theme(object):
self.name = name self.name = name
self.source = source self.source = source
self.use_color = use_color self.use_color = use_color
self._color_pair_map = None self._color_pair_map = None
self._attribute_map = None self._attribute_map = None
self._modifier = None self._selected = None
self.required_color_pairs = 0 self.required_color_pairs = 0
self.required_colors = 0 self.required_colors = 0
@@ -141,45 +196,32 @@ class Theme(object):
if elements is None: if elements is None:
elements = self.DEFAULT_THEME.copy() elements = self.DEFAULT_THEME.copy()
# Fill in any keywords that are defined in the default theme but were # Set any elements that weren't defined by the config to fallback to
# not passed into the elements dictionary. # the default color and attributes
for key in self.DEFAULT_THEME.keys(): for key in self.DEFAULT_THEME.keys():
# The "@normal"/"@highlight" are special elements that act as
# fallbacks for all of the other elements. They must always be
# defined and can't have the colors/attribute empty by setting
# them to "-" or None.
if key.startswith('@'):
if key not in elements:
elements[key] = self.DEFAULT_THEME[key]
continue
# Modifiers are handled below
if key.endswith('.highlight'):
continue
# Set undefined elements to fallback to the default color
if key not in elements: if key not in elements:
elements[key] = (None, None, None) elements[key] = (None, None, None)
# Set undefined highlight elements to match their base element self._set_fallback(elements, 'Normal', (-1, -1, curses.A_NORMAL))
modifier_key = key + '.highlight' self._set_fallback(elements, 'Selected', 'Normal')
if modifier_key not in elements: self._set_fallback(elements, 'SelectedCursor', 'Normal')
elements[modifier_key] = elements[key]
# At this point all of the possible keys should exist in the element map. # Most elements have two possible attribute states:
# Now we can "bubble up" the undefined attributes to copy the default # 1. The default state - inherits from "Normal"
# of the @normal and @highlight modifiers. # 2. The selected state - inherits from "Selected" and is
for key, val in elements.items(): # prefixed by the "@" sign.
if key.endswith('.highlight'): for name in self.SELECTED_ELEMENTS:
default = elements['@highlight'] dest = '@{0}'.format(name)
else: self._set_fallback(elements, name, 'Selected', dest)
default = elements['@normal'] self._set_fallback(elements, name, 'Normal')
elements[key] = ( for name in self.SELECTED_CURSOR_ELEMENTS:
default[0] if val[0] is None else val[0], dest = '@{0}'.format(name)
default[1] if val[1] is None else val[1], self._set_fallback(elements, name, 'SelectedCursor', dest)
default[2] if val[2] is None else val[2]) self._set_fallback(elements, name, 'Normal')
for name in self.PAGE_ELEMENTS:
self._set_fallback(elements, name, 'Normal')
self.elements = elements self.elements = elements
@@ -238,47 +280,43 @@ class Theme(object):
self._attribute_map[element] = attrs self._attribute_map[element] = attrs
def get(self, element, modifier=None): def get(self, element, selected=False):
""" """
Returns the curses attribute code for the given element. Returns the curses attribute code for the given element.
If element is None, return the background code (e.g. @normal).
""" """
if self._attribute_map is None: if self._attribute_map is None:
raise RuntimeError('Attempted to access theme attribute before ' raise RuntimeError('Attempted to access theme attribute before '
'calling initialize_curses_theme()') 'calling initialize_curses_theme()')
modifier = modifier or self._modifier if selected or self._selected:
element = '@{0}'.format(element)
if modifier and not element.startswith('@'):
element = element + '.' + modifier
return self._attribute_map[element] return self._attribute_map[element]
@contextmanager @contextmanager
def set_modifier(self, modifier=None): def turn_on_selected(self):
""" """
Sets the active modifier inside of context block. Sets the selected modifier inside of context block.
For example: For example:
>>> with theme.set_modifier('highlight'): >>> with theme.turn_on_selected():
>>> attr = theme.get('cursor') >>> attr = theme.get('CursorBlock')
Is the same as: Is the same as:
>>> attr = theme.get('cursor', modifier='highlight') >>> attr = theme.get('CursorBlock', selected=True)
Is also the same as: Is also the same as:
>>> attr = theme.get('cursor.highlight') >>> attr = theme.get('@CursorBlock')
""" """
# This case is undefined if the context manager is nested # This context manager should never be nested
assert self._modifier is None assert self._selected is None
self._modifier = modifier self._selected = True
try: try:
yield yield
finally: finally:
self._modifier = None self._selected = None
@classmethod @classmethod
def list_themes(cls, path=THEMES): def list_themes(cls, path=THEMES):
@@ -378,9 +416,11 @@ class Theme(object):
filename: The name of the filename to load. filename: The name of the filename to load.
source: A description of where the theme was loaded from. source: A description of where the theme was loaded from.
""" """
_logger.info('Loading theme %s', filename)
try: try:
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.optionxform = six.text_type # Preserve case
with codecs.open(filename, encoding='utf-8') as fp: with codecs.open(filename, encoding='utf-8') as fp:
config.readfp(fp) config.readfp(fp)
except configparser.ParsingError as e: except configparser.ParsingError as e:
@@ -399,6 +439,7 @@ class Theme(object):
if element not in cls.DEFAULT_THEME: if element not in cls.DEFAULT_THEME:
# Could happen if using a new config with an older version # Could happen if using a new config with an older version
# of the software # of the software
_logger.info('Skipping element %s', element)
continue continue
elements[element] = cls._parse_line(element, line, filename) elements[element] = cls._parse_line(element, line, filename)
@@ -453,13 +494,26 @@ class Theme(object):
else: else:
attrs_code |= attr_code attrs_code |= attr_code
if element.startswith('@') and None in (fg_code, bg_code, attrs_code):
raise ConfigError(
'Error loading {0}, {1} cannot have unspecified attributes:\n'
' {1} = {2}'.format(filename, element, line))
return fg_code, bg_code, attrs_code return fg_code, bg_code, attrs_code
@staticmethod
def _set_fallback(elements, src_field, fallback, dest_field=None):
"""
Helper function used to set the fallback attributes of an element when
they are defined by the configuration as "None" or "-".
"""
if dest_field is None:
dest_field = src_field
if isinstance(fallback, six.string_types):
fallback = elements[fallback]
attrs = elements[src_field]
elements[dest_field] = (
attrs[0] if attrs[0] is not None else fallback[0],
attrs[1] if attrs[1] is not None else fallback[1],
attrs[2] if attrs[2] is not None else fallback[2])
@staticmethod @staticmethod
def rgb_to_ansi(color): def rgb_to_ansi(color):
""" """

View File

@@ -1,52 +0,0 @@
[theme]
;<element> = <foreground> <background> <attributes>
@normal = default default normal
@highlight = default default normal
bar_level_1 = magenta - -
bar_level_1.highlight = magenta - reverse
bar_level_2 = cyan - -
bar_level_2.highlight = cyan - reverse
bar_level_3 = green - -
bar_level_3.highlight = green - reverse
bar_level_4 = yellow - -
bar_level_4.highlight = yellow - reverse
comment_author = blue - bold
comment_author_self = green - bold
comment_count = - - -
comment_text = - - -
created = - - -
cursor = - - -
cursor.highlight = - - reverse
downvote = red - bold
gold = yellow - bold
help_bar = cyan - bold+reverse
hidden_comment_expand = - - bold
hidden_comment_text = - - -
multireddit_name = yellow - bold
multireddit_text = - - -
neutral_vote = - - bold
notice_info = - - bold
notice_loading = - - bold
notice_error = red - bold
notice_success = green - bold
nsfw = red - bold+reverse
order_bar = yellow - bold
order_bar.highlight = yellow - bold+reverse
prompt = cyan - bold+reverse
saved = green - -
score = - - -
separator = - - bold
stickied = green - -
subscription_name = yellow - bold
subscription_text = - - -
submission_author = green - bold
submission_flair = red - -
submission_subreddit = yellow - -
submission_text = - - -
submission_title = - - bold
title_bar = cyan - bold+reverse
upvote = green - bold
url = blue - underline
url_seen = magenta - underline
user_flair = yellow - bold

View File

@@ -0,0 +1,53 @@
# http://www.calmar.ws/vim/256-xterm-24bit-rgb-color-chart.html
# https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg
[theme]
;<element> = <foreground> <background> <attributes>
Normal = default default -
Selected = - - -
SelectedCursor = - - reverse
PageTitle = cyan - bold+reverse
PageOrder = yellow - bold
PageOrderHighlight = yellow - bold+reverse
Help = cyan - bold+reverse
Prompt = cyan - bold+reverse
NoticeInfo = - - bold
NoticeLoading = - - bold
NoticeError = - - bold
NoticeSuccess = - - bold
CursorBlock = - - -
CursorBar1 = magenta - -
CursorBar2 = cyan - -
CursorBar3 = green - -
CursorBar4 = yellow - -
CommentAuthor = blue - bold
CommentAuthorSelf = green - bold
CommentCount = - - -
CommentText = - - -
Created = - - -
Downvote = red - bold
Gold = yellow - bold
HiddenCommentExpand = - - bold
HiddenCommentText = - - -
MultiredditName = yellow - bold
MultiredditText = - - -
NeutralVote = - - bold
NSFW = red - bold+reverse
Saved = green - -
Score = - - -
Separator = - - bold
Stickied = green - -
SubscriptionName = yellow - bold
SubscriptionText = - - -
SubmissionAuthor = green - bold
SubmissionFlair = red - -
SubmissionSubreddit = yellow - -
SubmissionText = - - -
SubmissionTitle = - - bold
Upvote = green - bold
Link = blue - underline
LinkSeen = magenta - underline
UserFlair = yellow - bold

View File

@@ -19,53 +19,51 @@
[theme] [theme]
;<element> = <foreground> <background> <attributes> ;<element> = <foreground> <background> <attributes>
@normal = ansi_244 ansi_234 normal Normal = ansi_244 ansi_234 -
@highlight = ansi_244 ansi_235 normal Selected = ansi_244 ansi_235 -
SelectedCursor = ansi_244 ansi_235 bold+reverse
bar_level_1 = ansi_125 - PageTitle = ansi_37 - bold+reverse
bar_level_1.highlight = ansi_125 - reverse PageOrder = ansi_240 - bold
bar_level_2 = ansi_160 - PageOrderHighlight = ansi_240 - bold+reverse
bar_level_2.highlight = ansi_125 - reverse Help = ansi_37 - bold+reverse
bar_level_3 = ansi_61 - Prompt = ansi_33 - bold+reverse
bar_level_3.highlight = ansi_125 - reverse NoticeInfo = - - bold
bar_level_4 = ansi_37 - NoticeLoading = - - bold
bar_level_4.highlight = ansi_125 - reverse NoticeError = ansi_160 - bold
comment_author = ansi_33 - bold NoticeSuccess = ansi_64 - bold
comment_author_self = ansi_64 - bold
comment_count = - - CursorBlock = ansi_240 - -
comment_text = - - CursorBar1 = ansi_125 - bold
created = - - CursorBar2 = ansi_160 - bold
cursor = - - CursorBar3 = ansi_61 - bold
cursor.highlight = ansi_240 - reverse CursorBar4 = ansi_37 - bold
downvote = ansi_160 - bold
gold = ansi_136 - bold CommentAuthor = ansi_33 - bold
help_bar = ansi_37 - bold+reverse CommentAuthorSelf = ansi_64 - bold
hidden_comment_expand = ansi_245 - bold CommentCount = - - -
hidden_comment_text = ansi_245 - CommentText = - - -
multireddit_name = ansi_240 - bold Created = - - -
multireddit_text = ansi_245 - Downvote = ansi_160 - bold
neutral_vote = - - bold Gold = ansi_136 - bold
notice_info = - - bold HiddenCommentExpand = ansi_245 - bold
notice_loading = - - bold HiddenCommentText = ansi_245 - -
notice_error = ansi_160 - bold MultiredditName = ansi_240 - bold
notice_success = ansi_64 - bold MultiredditText = ansi_245 - -
nsfw = ansi_125 - bold+reverse NeutralVote = - - bold
order_bar = ansi_245 - bold NSFW = ansi_125 - bold+reverse
order_bar.highlight = ansi_245 - bold+reverse Saved = ansi_125 - -
prompt = ansi_33 - bold+reverse Score = - - -
saved = ansi_125 - Separator = - - bold
score = - - Stickied = ansi_136 - -
separator = - - bold SubscriptionName = ansi_240 - bold
stickied = ansi_136 - SubscriptionText = ansi_245 - -
subscription_name = ansi_240 - bold SubmissionAuthor = ansi_64 - bold
subscription_text = ansi_245 - SubmissionFlair = ansi_160 - -
submission_author = ansi_64 - bold SubmissionSubreddit = ansi_166 - -
submission_flair = ansi_160 - SubmissionText = - - -
submission_subreddit = ansi_166 - SubmissionTitle = ansi_245 - bold
submission_text = - - Upvote = ansi_64 - bold
submission_title = ansi_244 - bold Link = ansi_33 - underline
title_bar = ansi_37 - bold+reverse LinkSeen = ansi_61 - underline
upvote = ansi_64 - bold UserFlair = ansi_136 - bold
url = ansi_33 - underline
url_seen = ansi_61 - underline
user_flair = ansi_136 - bold

View File

@@ -19,53 +19,51 @@
[theme] [theme]
;<element> = <foreground> <background> <attributes> ;<element> = <foreground> <background> <attributes>
@normal = ansi_241 ansi_230 normal Normal = ansi_241 ansi_230 -
@highlight = ansi_241 ansi_254 normal Selected = ansi_241 ansi_254 -
SelectedCursor = ansi_241 ansi_254 reverse
bar_level_1 = ansi_125 - PageTitle = ansi_37 - bold+reverse
bar_level_1.highlight = ansi_125 - reverse PageOrder = ansi_245 - bold
bar_level_2 = ansi_160 - PageOrderHighlight = ansi_245 - bold+reverse
bar_level_2.highlight = ansi_125 - reverse Help = ansi_37 - bold+reverse
bar_level_3 = ansi_61 - Prompt = ansi_33 - bold+reverse
bar_level_3.highlight = ansi_125 - reverse NoticeInfo = - - bold
bar_level_4 = ansi_37 - NoticeLoading = - - bold
bar_level_4.highlight = ansi_125 - reverse NoticeError = ansi_160 - bold
comment_author = ansi_33 - bold NoticeSuccess = ansi_64 - bold
comment_author_self = ansi_64 - bold
comment_count = - - CursorBlock = ansi_245 - -
comment_text = - - CursorBar1 = ansi_125 - -
created = - - CursorBar2 = ansi_160 - -
cursor = - - CursorBar3 = ansi_61 - -
cursor.highlight = ansi_245 - reverse CursorBar4 = ansi_37 - -
downvote = ansi_160 - bold
gold = ansi_136 - bold CommentAuthor = ansi_33 - bold
help_bar = ansi_37 - bold+reverse CommentAuthorSelf = ansi_64 - bold
hidden_comment_expand = ansi_245 - bold CommentCount = - - -
hidden_comment_text = ansi_245 - CommentText = - - -
multireddit_name = ansi_240 - bold Created = - - -
multireddit_text = ansi_245 - Downvote = ansi_160 - bold
neutral_vote = - - bold Gold = ansi_136 - bold
notice_info = - - bold HiddenCommentExpand = ansi_245 - bold
notice_loading = - - bold HiddenCommentText = ansi_245 - -
notice_error = ansi_160 - bold MultiredditName = ansi_240 - bold
notice_success = ansi_64 - bold MultiredditText = ansi_245 - -
nsfw = ansi_125 - bold+reverse NeutralVote = - - bold
order_bar = ansi_245 - bold NSFW = ansi_125 - bold+reverse
order_bar.highlight = ansi_245 - bold+reverse Saved = ansi_125 - -
prompt = ansi_33 - bold+reverse Score = - - -
saved = ansi_125 - Separator = - - bold
score = - - Stickied = ansi_136 - -
separator = - - bold SubscriptionName = ansi_240 - bold
stickied = ansi_136 - SubscriptionText = ansi_245 - -
subscription_name = ansi_240 - bold SubmissionAuthor = ansi_64 - bold
subscription_text = ansi_245 - SubmissionFlair = ansi_160 - -
submission_author = ansi_64 - bold SubmissionSubreddit = ansi_166 - -
submission_flair = ansi_160 - SubmissionText = - - -
submission_subreddit = ansi_166 - SubmissionTitle = ansi_240 - bold
submission_text = - - Upvote = ansi_64 - bold
submission_title = ansi_240 - bold Link = ansi_33 - underline
title_bar = ansi_37 - bold+reverse LinkSeen = ansi_61 - underline
upvote = ansi_64 - bold UserFlair = ansi_136 - bold
url = ansi_33 - underline
url_seen = ansi_61 - underline
user_flair = ansi_136 - bold