Implemented add_line wrapper to fix unicode bugs with curses.addnstr.
This commit is contained in:
@@ -6,12 +6,12 @@ from curses import textpad, ascii
|
||||
from contextlib import contextmanager
|
||||
|
||||
from .docs import HELP
|
||||
from .helpers import strip_textpad
|
||||
from .helpers import strip_textpad, clean
|
||||
from .exceptions import EscapeInterrupt
|
||||
|
||||
__all__ = ['ESCAPE', 'UARROW', 'DARROW', 'BULLET', 'show_notification',
|
||||
'show_help', 'LoadScreen', 'Color', 'text_input', 'curses_session',
|
||||
'prompt_input']
|
||||
'prompt_input', 'add_line']
|
||||
|
||||
ESCAPE = 27
|
||||
|
||||
@@ -25,6 +25,49 @@ DARROW = u'\u25bc'.encode('utf-8')
|
||||
BULLET = u'\u2022'.encode('utf-8')
|
||||
GOLD = u'\u272A'.encode('utf-8')
|
||||
|
||||
def add_line(window, text, row=None, col=None, attr=None):
|
||||
"""
|
||||
Unicode aware version of curses's built-in addnstr method.
|
||||
|
||||
Safely draws a line of text on the window starting at position (row, col).
|
||||
Checks the boundaries of the window and cuts off the text if it exceeds
|
||||
the length of the window.
|
||||
"""
|
||||
|
||||
# The following arg combinations must be supported to conform with addnstr
|
||||
# (window, text)
|
||||
# (window, text, attr)
|
||||
# (window, text, row, col)
|
||||
# (window, text, row, col, attr)
|
||||
|
||||
# Text must be unicode or ascii. Can't be UTF-8!
|
||||
text = clean(text)
|
||||
|
||||
cursor_row, cursor_col = window.getyx()
|
||||
row = row if row is not None else cursor_row
|
||||
col = col if col is not None else cursor_col
|
||||
|
||||
max_rows, max_cols = window.getmaxyx()
|
||||
n_cols = max_cols - col - 1
|
||||
if n_cols <= 0:
|
||||
# Trying to draw outside of the screen bounds
|
||||
return
|
||||
|
||||
# We have n_cols available to draw the text. Add characters to a text buffer
|
||||
# until we reach the end of the screen
|
||||
buffer, space_left = [], n_cols
|
||||
for char in text:
|
||||
space_left -= unicode_width(char)
|
||||
if space_left < 0:
|
||||
break
|
||||
buffer.append(char)
|
||||
|
||||
trimmed_text = ''.join(buffer)
|
||||
if attr is None:
|
||||
window.addnstr(row, col, trimmed_text, n_cols)
|
||||
else:
|
||||
window.addnstr(row, col, trimmed_text, n_cols, attr)
|
||||
|
||||
|
||||
def show_notification(stdscr, message):
|
||||
"""
|
||||
@@ -52,7 +95,7 @@ def show_notification(stdscr, message):
|
||||
window.border()
|
||||
|
||||
for index, line in enumerate(message, start=1):
|
||||
window.addnstr(index, 1, line, box_width - 2)
|
||||
add_line(window, line, index, 1)
|
||||
window.refresh()
|
||||
ch = stdscr.getch()
|
||||
|
||||
|
||||
13
rtv/page.py
13
rtv/page.py
@@ -8,9 +8,9 @@ from contextlib import contextmanager
|
||||
import praw.errors
|
||||
import requests
|
||||
|
||||
from .helpers import clean, open_editor
|
||||
from .helpers import open_editor
|
||||
from .curses_helpers import (Color, show_notification, show_help, text_input,
|
||||
prompt_input)
|
||||
prompt_input, add_line)
|
||||
from .docs import COMMENT_EDIT_FILE, SUBMISSION_FILE
|
||||
|
||||
__all__ = ['Navigator', 'BaseController', 'BasePage']
|
||||
@@ -470,17 +470,16 @@ class BasePage(object):
|
||||
self._header_window.bkgd(' ', attr)
|
||||
|
||||
sub_name = self.content.name.replace('/r/front', 'Front Page ')
|
||||
sub_name = 'blank'
|
||||
self._header_window.addnstr(0, 0, clean(sub_name), n_cols - 1)
|
||||
add_line(self._header_window, sub_name, 0, 0)
|
||||
|
||||
if self.reddit.user is not None:
|
||||
username = self.reddit.user.name
|
||||
# TODO: use unicode width here instead of length
|
||||
s_col = (n_cols - len(username) - 1)
|
||||
# Only print the username if it fits in the empty space on the
|
||||
# right
|
||||
# Only print 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)
|
||||
add_line(self._header_window, username, 0, s_col)
|
||||
|
||||
self._header_window.refresh()
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ import praw.errors
|
||||
|
||||
from .content import SubmissionContent
|
||||
from .page import BasePage, Navigator, BaseController
|
||||
from .helpers import clean, open_browser, open_editor
|
||||
from .helpers import open_browser, open_editor
|
||||
from .curses_helpers import (BULLET, UARROW, DARROW, GOLD, Color, LoadScreen,
|
||||
show_notification, text_input)
|
||||
show_notification, text_input, add_line)
|
||||
from .docs import COMMENT_FILE
|
||||
|
||||
__all__ = ['SubmissionController', 'SubmissionPage']
|
||||
@@ -158,15 +158,13 @@ class SubmissionPage(BasePage):
|
||||
row = offset
|
||||
if row in valid_rows:
|
||||
|
||||
text = clean(u'{author} '.format(**data))
|
||||
attr = curses.A_BOLD
|
||||
attr |= (Color.BLUE if not data['is_author'] else Color.GREEN)
|
||||
win.addnstr(row, 1, text, n_cols - 1, attr)
|
||||
add_line(win, u'{author} '.format(**data), row, 1, attr)
|
||||
|
||||
if data['flair']:
|
||||
text = clean(u'{flair} '.format(**data))
|
||||
attr = curses.A_BOLD | Color.YELLOW
|
||||
win.addnstr(text, n_cols - win.getyx()[1], attr)
|
||||
add_line(win, u'{flair} '.format(**data), attr=attr)
|
||||
|
||||
if data['likes'] is None:
|
||||
text, attr = BULLET, curses.A_BOLD
|
||||
@@ -174,20 +172,18 @@ class SubmissionPage(BasePage):
|
||||
text, attr = UARROW, (curses.A_BOLD | Color.GREEN)
|
||||
else:
|
||||
text, attr = DARROW, (curses.A_BOLD | Color.RED)
|
||||
win.addnstr(text, n_cols - win.getyx()[1], attr)
|
||||
add_line(win, text, attr=attr)
|
||||
|
||||
text = clean(u' {score} {created} '.format(**data))
|
||||
win.addnstr(text, n_cols - win.getyx()[1])
|
||||
add_line(win, u' {score} {created} '.format(**data))
|
||||
|
||||
if data['gold']:
|
||||
text, attr = GOLD, (curses.A_BOLD | Color.YELLOW)
|
||||
win.addnstr(text, n_cols - win.getyx()[1], attr)
|
||||
add_line(win, text, attr=attr)
|
||||
|
||||
n_body = len(data['split_body'])
|
||||
for row, text in enumerate(data['split_body'], start=offset + 1):
|
||||
if row in valid_rows:
|
||||
text = clean(text)
|
||||
win.addnstr(row, 1, text, n_cols - 1)
|
||||
add_line(win, text, row, 1)
|
||||
|
||||
# Unfortunately vline() doesn't support custom color so we have to
|
||||
# build it one segment at a time.
|
||||
@@ -210,13 +206,9 @@ class SubmissionPage(BasePage):
|
||||
n_rows, n_cols = win.getmaxyx()
|
||||
n_cols -= 1
|
||||
|
||||
text = clean(u'{body}'.format(**data))
|
||||
win.addnstr(0, 1, text, n_cols - 1)
|
||||
text = clean(u' [{count}]'.format(**data))
|
||||
win.addnstr(text, n_cols - win.getyx()[1], curses.A_BOLD)
|
||||
add_line(win, u'{body}'.format(**data), 0, 1)
|
||||
add_line(win, u' [{count}]'.format(**data), attr=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'])
|
||||
win.addch(0, 0, curses.ACS_VLINE, attr)
|
||||
|
||||
@@ -229,23 +221,18 @@ class SubmissionPage(BasePage):
|
||||
n_cols -= 3 # one for each side of the border + one for offset
|
||||
|
||||
for row, text in enumerate(data['split_title'], start=1):
|
||||
text = clean(text)
|
||||
win.addnstr(row, 1, text, n_cols, curses.A_BOLD)
|
||||
add_line(win, text, row, 1, curses.A_BOLD)
|
||||
|
||||
row = len(data['split_title']) + 1
|
||||
attr = curses.A_BOLD | Color.GREEN
|
||||
text = clean(u'{author}'.format(**data))
|
||||
win.addnstr(row, 1, text, n_cols, attr)
|
||||
add_line(win, u'{author}'.format(**data), row, 1, attr)
|
||||
attr = curses.A_BOLD | Color.YELLOW
|
||||
text = clean(u' {flair}'.format(**data))
|
||||
win.addnstr(text, n_cols - win.getyx()[1], attr)
|
||||
text = clean(u' {created} {subreddit}'.format(**data))
|
||||
win.addnstr(text, n_cols - win.getyx()[1])
|
||||
add_line(win, u' {flair}'.format(**data), attr=attr)
|
||||
add_line(win, u' {created} {subreddit}'.format(**data))
|
||||
|
||||
row = len(data['split_title']) + 2
|
||||
attr = curses.A_UNDERLINE | Color.BLUE
|
||||
text = clean(u'{url}'.format(**data))
|
||||
win.addnstr(row, 1, text, n_cols, attr)
|
||||
add_line(win, u'{url}'.format(**data), row, 1, attr)
|
||||
offset = len(data['split_title']) + 3
|
||||
|
||||
# Cut off text if there is not enough room to display the whole post
|
||||
@@ -256,12 +243,10 @@ class SubmissionPage(BasePage):
|
||||
split_text.append('(Not enough space to display)')
|
||||
|
||||
for row, text in enumerate(split_text, start=offset):
|
||||
text = clean(text)
|
||||
win.addnstr(row, 1, text, n_cols)
|
||||
add_line(win, text, row, 1)
|
||||
|
||||
row = len(data['split_title']) + len(split_text) + 3
|
||||
text = clean(u'{score} '.format(**data))
|
||||
win.addnstr(row, 1, text, n_cols - 1)
|
||||
add_line(win, u'{score} '.format(**data), row, 1)
|
||||
|
||||
if data['likes'] is None:
|
||||
text, attr = BULLET, curses.A_BOLD
|
||||
@@ -269,17 +254,15 @@ class SubmissionPage(BasePage):
|
||||
text, attr = UARROW, curses.A_BOLD | Color.GREEN
|
||||
else:
|
||||
text, attr = DARROW, curses.A_BOLD | Color.RED
|
||||
win.addnstr(text, n_cols - win.getyx()[1], attr)
|
||||
|
||||
text = clean(u' {comments} '.format(**data))
|
||||
win.addnstr(text, n_cols - win.getyx()[1])
|
||||
add_line(win, text, attr=attr)
|
||||
add_line(win, u' {comments} '.format(**data))
|
||||
|
||||
if data['gold']:
|
||||
text, attr = GOLD, (curses.A_BOLD | Color.YELLOW)
|
||||
win.addnstr(text, n_cols - win.getyx()[1], attr)
|
||||
add_line(win, text, attr=attr)
|
||||
|
||||
if data['nsfw']:
|
||||
text, attr = 'NSFW', (curses.A_BOLD | Color.RED)
|
||||
win.addnstr(text, n_cols - win.getyx()[1], attr)
|
||||
add_line(win, text, attr=attr)
|
||||
|
||||
win.border()
|
||||
@@ -9,9 +9,9 @@ from .exceptions import SubredditError, AccountError
|
||||
from .page import BasePage, Navigator, BaseController
|
||||
from .submission import SubmissionPage
|
||||
from .content import SubredditContent
|
||||
from .helpers import clean, open_browser, open_editor
|
||||
from .helpers import open_browser, open_editor
|
||||
from .docs import SUBMISSION_FILE
|
||||
from .curses_helpers import (BULLET, UARROW, DARROW, GOLD, Color,
|
||||
from .curses_helpers import (BULLET, UARROW, DARROW, GOLD, Color, add_line,
|
||||
LoadScreen, show_notification, prompt_input)
|
||||
|
||||
__all__ = ['opened_links', 'SubredditController', 'SubredditPage']
|
||||
@@ -163,21 +163,18 @@ class SubredditPage(BasePage):
|
||||
n_title = len(data['split_title'])
|
||||
for row, text in enumerate(data['split_title'], start=offset):
|
||||
if row in valid_rows:
|
||||
text = clean(text)
|
||||
win.addnstr(row, 1, text, n_cols - 1, curses.A_BOLD)
|
||||
add_line(win, text, row, 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 = clean(u'{url}'.format(**data))
|
||||
win.addnstr(row, 1, text, n_cols - 1, attr)
|
||||
add_line(win, u'{url}'.format(**data), row, 1, attr)
|
||||
|
||||
row = n_title + offset + 1
|
||||
if row in valid_rows:
|
||||
text = clean(u'{score} '.format(**data))
|
||||
win.addnstr(row, 1, text, n_cols - 1)
|
||||
add_line(win, u'{score} '.format(**data), row, 1)
|
||||
|
||||
if data['likes'] is None:
|
||||
text, attr = BULLET, curses.A_BOLD
|
||||
@@ -185,24 +182,19 @@ class SubredditPage(BasePage):
|
||||
text, attr = UARROW, curses.A_BOLD | Color.GREEN
|
||||
else:
|
||||
text, attr = DARROW, curses.A_BOLD | Color.RED
|
||||
win.addnstr(text, n_cols - win.getyx()[1], attr)
|
||||
|
||||
text = clean(u' {created} {comments} '.format(**data))
|
||||
win.addnstr(text, n_cols - win.getyx()[1])
|
||||
add_line(win, text, attr=attr)
|
||||
add_line(win, u' {created} {comments} '.format(**data))
|
||||
|
||||
if data['gold']:
|
||||
text, attr = GOLD, (curses.A_BOLD | Color.YELLOW)
|
||||
win.addnstr(text, n_cols - win.getyx()[1], attr)
|
||||
add_line(win, text, attr=attr)
|
||||
|
||||
if data['nsfw']:
|
||||
text, attr = 'NSFW', (curses.A_BOLD | Color.RED)
|
||||
win.addnstr(text, n_cols - win.getyx()[1], attr)
|
||||
add_line(win, text, attr=attr)
|
||||
|
||||
row = n_title + offset + 2
|
||||
if row in valid_rows:
|
||||
text = clean(u'{author}'.format(**data))
|
||||
win.addnstr(row, 1, text, n_cols - 1, curses.A_BOLD)
|
||||
text = clean(u' {subreddit}'.format(**data))
|
||||
win.addnstr(text, n_cols - win.getyx()[1], Color.YELLOW)
|
||||
text = clean(u' {flair}'.format(**data))
|
||||
win.addnstr(text, n_cols - win.getyx()[1], Color.RED)
|
||||
add_line(win, u'{author}'.format(**data), row, 1, curses.A_BOLD)
|
||||
add_line(win, u' {subreddit}'.format(**data), attr=Color.YELLOW)
|
||||
add_line(Win, u' {flair}'.format(**data), attr=Color.RED)
|
||||
Reference in New Issue
Block a user