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 contextlib import contextmanager
|
||||||
|
|
||||||
from .docs import HELP
|
from .docs import HELP
|
||||||
from .helpers import strip_textpad
|
from .helpers import strip_textpad, clean
|
||||||
from .exceptions import EscapeInterrupt
|
from .exceptions import EscapeInterrupt
|
||||||
|
|
||||||
__all__ = ['ESCAPE', 'UARROW', 'DARROW', 'BULLET', 'show_notification',
|
__all__ = ['ESCAPE', 'UARROW', 'DARROW', 'BULLET', 'show_notification',
|
||||||
'show_help', 'LoadScreen', 'Color', 'text_input', 'curses_session',
|
'show_help', 'LoadScreen', 'Color', 'text_input', 'curses_session',
|
||||||
'prompt_input']
|
'prompt_input', 'add_line']
|
||||||
|
|
||||||
ESCAPE = 27
|
ESCAPE = 27
|
||||||
|
|
||||||
@@ -25,6 +25,49 @@ DARROW = u'\u25bc'.encode('utf-8')
|
|||||||
BULLET = u'\u2022'.encode('utf-8')
|
BULLET = u'\u2022'.encode('utf-8')
|
||||||
GOLD = u'\u272A'.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):
|
def show_notification(stdscr, message):
|
||||||
"""
|
"""
|
||||||
@@ -52,7 +95,7 @@ def show_notification(stdscr, message):
|
|||||||
window.border()
|
window.border()
|
||||||
|
|
||||||
for index, line in enumerate(message, start=1):
|
for index, line in enumerate(message, start=1):
|
||||||
window.addnstr(index, 1, line, box_width - 2)
|
add_line(window, line, index, 1)
|
||||||
window.refresh()
|
window.refresh()
|
||||||
ch = stdscr.getch()
|
ch = stdscr.getch()
|
||||||
|
|
||||||
|
|||||||
13
rtv/page.py
13
rtv/page.py
@@ -8,9 +8,9 @@ from contextlib import contextmanager
|
|||||||
import praw.errors
|
import praw.errors
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .helpers import clean, open_editor
|
from .helpers import open_editor
|
||||||
from .curses_helpers import (Color, show_notification, show_help, text_input,
|
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
|
from .docs import COMMENT_EDIT_FILE, SUBMISSION_FILE
|
||||||
|
|
||||||
__all__ = ['Navigator', 'BaseController', 'BasePage']
|
__all__ = ['Navigator', 'BaseController', 'BasePage']
|
||||||
@@ -470,17 +470,16 @@ class BasePage(object):
|
|||||||
self._header_window.bkgd(' ', attr)
|
self._header_window.bkgd(' ', attr)
|
||||||
|
|
||||||
sub_name = self.content.name.replace('/r/front', 'Front Page ')
|
sub_name = self.content.name.replace('/r/front', 'Front Page ')
|
||||||
sub_name = 'blank'
|
add_line(self._header_window, sub_name, 0, 0)
|
||||||
self._header_window.addnstr(0, 0, clean(sub_name), n_cols - 1)
|
|
||||||
|
|
||||||
if self.reddit.user is not None:
|
if self.reddit.user is not None:
|
||||||
username = self.reddit.user.name
|
username = self.reddit.user.name
|
||||||
|
# TODO: use unicode width here instead of length
|
||||||
s_col = (n_cols - len(username) - 1)
|
s_col = (n_cols - len(username) - 1)
|
||||||
# Only print the username if it fits in the empty space on the
|
# Only print username if it fits in the empty space on the right
|
||||||
# right
|
|
||||||
if (s_col - 1) >= len(sub_name):
|
if (s_col - 1) >= len(sub_name):
|
||||||
n = (n_cols - s_col - 1)
|
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()
|
self._header_window.refresh()
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import praw.errors
|
|||||||
|
|
||||||
from .content import SubmissionContent
|
from .content import SubmissionContent
|
||||||
from .page import BasePage, Navigator, BaseController
|
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,
|
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
|
from .docs import COMMENT_FILE
|
||||||
|
|
||||||
__all__ = ['SubmissionController', 'SubmissionPage']
|
__all__ = ['SubmissionController', 'SubmissionPage']
|
||||||
@@ -158,15 +158,13 @@ class SubmissionPage(BasePage):
|
|||||||
row = offset
|
row = offset
|
||||||
if row in valid_rows:
|
if row in valid_rows:
|
||||||
|
|
||||||
text = clean(u'{author} '.format(**data))
|
|
||||||
attr = curses.A_BOLD
|
attr = curses.A_BOLD
|
||||||
attr |= (Color.BLUE if not data['is_author'] else Color.GREEN)
|
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']:
|
if data['flair']:
|
||||||
text = clean(u'{flair} '.format(**data))
|
|
||||||
attr = curses.A_BOLD | Color.YELLOW
|
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:
|
if data['likes'] is None:
|
||||||
text, attr = BULLET, curses.A_BOLD
|
text, attr = BULLET, curses.A_BOLD
|
||||||
@@ -174,20 +172,18 @@ class SubmissionPage(BasePage):
|
|||||||
text, attr = UARROW, (curses.A_BOLD | Color.GREEN)
|
text, attr = UARROW, (curses.A_BOLD | Color.GREEN)
|
||||||
else:
|
else:
|
||||||
text, attr = DARROW, (curses.A_BOLD | Color.RED)
|
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))
|
add_line(win, u' {score} {created} '.format(**data))
|
||||||
win.addnstr(text, n_cols - win.getyx()[1])
|
|
||||||
|
|
||||||
if data['gold']:
|
if data['gold']:
|
||||||
text, attr = GOLD, (curses.A_BOLD | Color.YELLOW)
|
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'])
|
n_body = len(data['split_body'])
|
||||||
for row, text in enumerate(data['split_body'], start=offset + 1):
|
for row, text in enumerate(data['split_body'], start=offset + 1):
|
||||||
if row in valid_rows:
|
if row in valid_rows:
|
||||||
text = clean(text)
|
add_line(win, text, row, 1)
|
||||||
win.addnstr(row, 1, text, n_cols - 1)
|
|
||||||
|
|
||||||
# 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.
|
||||||
@@ -210,13 +206,9 @@ class SubmissionPage(BasePage):
|
|||||||
n_rows, n_cols = win.getmaxyx()
|
n_rows, n_cols = win.getmaxyx()
|
||||||
n_cols -= 1
|
n_cols -= 1
|
||||||
|
|
||||||
text = clean(u'{body}'.format(**data))
|
add_line(win, u'{body}'.format(**data), 0, 1)
|
||||||
win.addnstr(0, 1, text, n_cols - 1)
|
add_line(win, u' [{count}]'.format(**data), attr=curses.A_BOLD)
|
||||||
text = clean(u' [{count}]'.format(**data))
|
|
||||||
win.addnstr(text, n_cols - win.getyx()[1], curses.A_BOLD)
|
|
||||||
|
|
||||||
# Unfortunately vline() doesn't support custom color so we have to
|
|
||||||
# build it one segment at a time.
|
|
||||||
attr = Color.get_level(data['level'])
|
attr = Color.get_level(data['level'])
|
||||||
win.addch(0, 0, curses.ACS_VLINE, attr)
|
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
|
n_cols -= 3 # one for each side of the border + one for offset
|
||||||
|
|
||||||
for row, text in enumerate(data['split_title'], start=1):
|
for row, text in enumerate(data['split_title'], start=1):
|
||||||
text = clean(text)
|
add_line(win, text, row, 1, curses.A_BOLD)
|
||||||
win.addnstr(row, 1, text, n_cols, curses.A_BOLD)
|
|
||||||
|
|
||||||
row = len(data['split_title']) + 1
|
row = len(data['split_title']) + 1
|
||||||
attr = curses.A_BOLD | Color.GREEN
|
attr = curses.A_BOLD | Color.GREEN
|
||||||
text = clean(u'{author}'.format(**data))
|
add_line(win, u'{author}'.format(**data), row, 1, attr)
|
||||||
win.addnstr(row, 1, text, n_cols, attr)
|
|
||||||
attr = curses.A_BOLD | Color.YELLOW
|
attr = curses.A_BOLD | Color.YELLOW
|
||||||
text = clean(u' {flair}'.format(**data))
|
add_line(win, u' {flair}'.format(**data), attr=attr)
|
||||||
win.addnstr(text, n_cols - win.getyx()[1], attr)
|
add_line(win, u' {created} {subreddit}'.format(**data))
|
||||||
text = clean(u' {created} {subreddit}'.format(**data))
|
|
||||||
win.addnstr(text, n_cols - win.getyx()[1])
|
|
||||||
|
|
||||||
row = len(data['split_title']) + 2
|
row = len(data['split_title']) + 2
|
||||||
attr = curses.A_UNDERLINE | Color.BLUE
|
attr = curses.A_UNDERLINE | Color.BLUE
|
||||||
text = clean(u'{url}'.format(**data))
|
add_line(win, u'{url}'.format(**data), row, 1, attr)
|
||||||
win.addnstr(row, 1, text, n_cols, attr)
|
|
||||||
offset = len(data['split_title']) + 3
|
offset = len(data['split_title']) + 3
|
||||||
|
|
||||||
# Cut off text if there is not enough room to display the whole post
|
# 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)')
|
split_text.append('(Not enough space to display)')
|
||||||
|
|
||||||
for row, text in enumerate(split_text, start=offset):
|
for row, text in enumerate(split_text, start=offset):
|
||||||
text = clean(text)
|
add_line(win, text, row, 1)
|
||||||
win.addnstr(row, 1, text, n_cols)
|
|
||||||
|
|
||||||
row = len(data['split_title']) + len(split_text) + 3
|
row = len(data['split_title']) + len(split_text) + 3
|
||||||
text = clean(u'{score} '.format(**data))
|
add_line(win, u'{score} '.format(**data), row, 1)
|
||||||
win.addnstr(row, 1, text, n_cols - 1)
|
|
||||||
|
|
||||||
if data['likes'] is None:
|
if data['likes'] is None:
|
||||||
text, attr = BULLET, curses.A_BOLD
|
text, attr = BULLET, curses.A_BOLD
|
||||||
@@ -269,17 +254,15 @@ class SubmissionPage(BasePage):
|
|||||||
text, attr = UARROW, curses.A_BOLD | Color.GREEN
|
text, attr = UARROW, curses.A_BOLD | Color.GREEN
|
||||||
else:
|
else:
|
||||||
text, attr = DARROW, curses.A_BOLD | Color.RED
|
text, attr = DARROW, curses.A_BOLD | Color.RED
|
||||||
win.addnstr(text, n_cols - win.getyx()[1], attr)
|
add_line(win, text, attr=attr)
|
||||||
|
add_line(win, u' {comments} '.format(**data))
|
||||||
text = clean(u' {comments} '.format(**data))
|
|
||||||
win.addnstr(text, n_cols - win.getyx()[1])
|
|
||||||
|
|
||||||
if data['gold']:
|
if data['gold']:
|
||||||
text, attr = GOLD, (curses.A_BOLD | Color.YELLOW)
|
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']:
|
if data['nsfw']:
|
||||||
text, attr = 'NSFW', (curses.A_BOLD | Color.RED)
|
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()
|
win.border()
|
||||||
@@ -9,9 +9,9 @@ from .exceptions import SubredditError, AccountError
|
|||||||
from .page import BasePage, Navigator, BaseController
|
from .page import BasePage, Navigator, BaseController
|
||||||
from .submission import SubmissionPage
|
from .submission import SubmissionPage
|
||||||
from .content import SubredditContent
|
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 .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)
|
LoadScreen, show_notification, prompt_input)
|
||||||
|
|
||||||
__all__ = ['opened_links', 'SubredditController', 'SubredditPage']
|
__all__ = ['opened_links', 'SubredditController', 'SubredditPage']
|
||||||
@@ -163,21 +163,18 @@ class SubredditPage(BasePage):
|
|||||||
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):
|
||||||
if row in valid_rows:
|
if row in valid_rows:
|
||||||
text = clean(text)
|
add_line(win, text, row, 1, curses.A_BOLD)
|
||||||
win.addnstr(row, 1, text, n_cols - 1, curses.A_BOLD)
|
|
||||||
|
|
||||||
row = n_title + offset
|
row = n_title + offset
|
||||||
if row in valid_rows:
|
if row in valid_rows:
|
||||||
seen = (data['url_full'] in opened_links)
|
seen = (data['url_full'] in opened_links)
|
||||||
link_color = Color.MAGENTA if seen else Color.BLUE
|
link_color = Color.MAGENTA if seen else Color.BLUE
|
||||||
attr = curses.A_UNDERLINE | link_color
|
attr = curses.A_UNDERLINE | link_color
|
||||||
text = clean(u'{url}'.format(**data))
|
add_line(win, u'{url}'.format(**data), row, 1, attr)
|
||||||
win.addnstr(row, 1, text, n_cols - 1, attr)
|
|
||||||
|
|
||||||
row = n_title + offset + 1
|
row = n_title + offset + 1
|
||||||
if row in valid_rows:
|
if row in valid_rows:
|
||||||
text = clean(u'{score} '.format(**data))
|
add_line(win, u'{score} '.format(**data), row, 1)
|
||||||
win.addnstr(row, 1, text, n_cols - 1)
|
|
||||||
|
|
||||||
if data['likes'] is None:
|
if data['likes'] is None:
|
||||||
text, attr = BULLET, curses.A_BOLD
|
text, attr = BULLET, curses.A_BOLD
|
||||||
@@ -185,24 +182,19 @@ class SubredditPage(BasePage):
|
|||||||
text, attr = UARROW, curses.A_BOLD | Color.GREEN
|
text, attr = UARROW, curses.A_BOLD | Color.GREEN
|
||||||
else:
|
else:
|
||||||
text, attr = DARROW, curses.A_BOLD | Color.RED
|
text, attr = DARROW, curses.A_BOLD | Color.RED
|
||||||
win.addnstr(text, n_cols - win.getyx()[1], attr)
|
add_line(win, text, attr=attr)
|
||||||
|
add_line(win, u' {created} {comments} '.format(**data))
|
||||||
text = clean(u' {created} {comments} '.format(**data))
|
|
||||||
win.addnstr(text, n_cols - win.getyx()[1])
|
|
||||||
|
|
||||||
if data['gold']:
|
if data['gold']:
|
||||||
text, attr = GOLD, (curses.A_BOLD | Color.YELLOW)
|
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']:
|
if data['nsfw']:
|
||||||
text, attr = 'NSFW', (curses.A_BOLD | Color.RED)
|
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
|
row = n_title + offset + 2
|
||||||
if row in valid_rows:
|
if row in valid_rows:
|
||||||
text = clean(u'{author}'.format(**data))
|
add_line(win, u'{author}'.format(**data), row, 1, curses.A_BOLD)
|
||||||
win.addnstr(row, 1, text, n_cols - 1, curses.A_BOLD)
|
add_line(win, u' {subreddit}'.format(**data), attr=Color.YELLOW)
|
||||||
text = clean(u' {subreddit}'.format(**data))
|
add_line(Win, u' {flair}'.format(**data), attr=Color.RED)
|
||||||
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)
|
|
||||||
Reference in New Issue
Block a user