Implemented add_line wrapper to fix unicode bugs with curses.addnstr.

This commit is contained in:
Michael Lazar
2015-05-02 16:23:02 -07:00
parent 49a9147ca6
commit 02e9e5e399
4 changed files with 85 additions and 68 deletions

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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)