Source code for x84.bbs.pager

""" Pager package for x/84. """
from x84.bbs.ansiwin import AnsiWindow
from x84.bbs.output import encode_pipe, decode_pipe
from x84.bbs.session import getterminal
from x84.bbs.output import echo

VI_KEYSET = {
    'refresh': [unichr(12), ],
    'home': [u'0'],
    'end': [u'G'],
    'up': [u'k', u'K'],
    'down': [u'j', u'J', u'\r'],
    'pgup': [u'b', u'B', u''],
    'pgdown': [u'f', u'F', u''],
    'exit': [u'q', u'Q', unichr(27), ],
}


[docs]class Pager(AnsiWindow): """ Scrolling viewer. """ def __init__(self, *args, **kwargs): """ Class initializer. :param int width: width of window. :param int height: height of window. :param int yloc: y-location of window. :param int xloc: x-location of window. :param str content: initial pager contents. :param dict colors: color theme. :param dict glyphs: bordering window character glyphs. :param dict keyset: command keys, global ``VI_KEYSET`` is default. """ self._quit = False self.init_keystrokes(keyset=kwargs.pop('keyset', VI_KEYSET.copy())) _content = kwargs.pop('content', u'') or u'' AnsiWindow.__init__(self, *args, **kwargs) self.content = _content self._position = self.position = kwargs.pop('position', 0) or 0
[docs] def init_keystrokes(self, keyset): """ Sets keyboard keys for various editing keystrokes. """ term = getterminal() self.keyset = keyset self.keyset['home'].append(term.KEY_HOME) self.keyset['end'].append(term.KEY_END) self.keyset['pgup'].append(term.KEY_PGUP) self.keyset['pgdown'].append(term.KEY_PGDOWN) self.keyset['up'].append(term.KEY_UP) self.keyset['down'].append(term.KEY_DOWN) self.keyset['down'].append(term.KEY_ENTER) self.keyset['exit'].append(term.KEY_ESCAPE)
@property def quit(self): """ Whether a 'quit' character has been handled, such as escape. """ return self._quit @property def position_last(self): """ Previous position before last move. """ return self._position_last @property def position(self): """ Index of content buffer displayed at top of window. """ return self._position @position.setter def position(self, pos): # pylint: disable=C0111 # Missing docstring self._position_last = self._position # assign and bounds check self._position = min(max(0, pos), self.bottom) self.moved = (self._position_last != self._position) @property def visible_content(self): """ Content that is visible in window. """ return self._content[self.position:self.position + self.visible_height] @property def visible_bottom(self): """ Bottom-most window row that contains content. """ if self.bottom < self.visible_height: return self.bottom return len(self.visible_content) - 1 @property def bottom(self): """ Bottom-most position that contains content. """ maximum = self.visible_height return max(0, maximum - self.visible_height)
[docs] def process_keystroke(self, keystroke): """ Process the keystroke and return string to refresh. :param blessed.keyboard.Keystroke keystroke: input from ``inkey()``. :rtype: str :returns: string sequence suitable for refresh. """ self.moved = False rstr = u'' if (keystroke in self.keyset['refresh'] or keystroke.code in self.keyset['refresh']): rstr += self.refresh() elif (keystroke in self.keyset['up'] or keystroke.code in self.keyset['up']): rstr += self.move_up() elif (keystroke in self.keyset['down'] or keystroke.code in self.keyset['down']): rstr += self.move_down() elif (keystroke in self.keyset['home'] or keystroke.code in self.keyset['home']): rstr += self.move_home() elif (keystroke in self.keyset['end'] or keystroke.code in self.keyset['end']): rstr += self.move_end() elif (keystroke in self.keyset['pgup'] or keystroke.code in self.keyset['pgup']): rstr += self.move_pgup() elif (keystroke in self.keyset['pgdown'] or keystroke.code in self.keyset['pgdown']): rstr += self.move_pgdown() elif (keystroke in self.keyset['exit'] or keystroke.code in self.keyset['exit']): self._quit = True return rstr
[docs] def read(self): """ Blocking read-eval-print loop for pager. Processes user input, taking action upon and refreshing pager until the escape key is pressed. :rtype: None """ self._quit = False echo(self.refresh()) term = getterminal() while not self.quit: echo(self.process_keystroke(term.inkey()))
[docs] def move_home(self): """ Scroll to top and return refresh string. :rtype: str """ self.position = 0 if self.moved: return self.refresh() return u''
[docs] def move_end(self): """ Scroll to bottom and return refresh string. :rtype: str """ self.position = len(self._content) - self.visible_height if self.moved: return self.refresh() return u''
[docs] def move_pgup(self, num=1): """ Scroll up ``num`` pages and return refresh string. :rtype: str """ self.position -= (num * (self.visible_height)) return self.refresh() if self.moved else u''
[docs] def move_pgdown(self, num=1): """ Scroll down ``num`` pages and return refresh string. :rtype: str """ self.position += (num * (self.visible_height)) return self.refresh() if self.moved else u''
[docs] def move_down(self, num=1): """ Scroll down ``num`` rows and return refresh string. :rtype: str """ self.position += num if self.moved: return self.refresh() return u''
[docs] def move_up(self, num=1): """ Scroll up ``num`` rows and return refresh string. :rtype: str """ self.position -= num if self.moved: return self.refresh() return u''
[docs] def refresh_row(self, row): """ Return unicode string suitable for refreshing pager row. :param int row: target row by visible index. :rtype: str """ term = getterminal() ucs = u'' if row < len(self.visible_content): ucs = self.visible_content[row] disp_position = self.pos(row + self.ypadding, self.xpadding) return u''.join((term.normal, disp_position, self.align(ucs), term.normal))
[docs] def refresh(self, start_row=0): """ Return unicode string suitable for refreshing pager window. :param int start_row: refresh from only visible row 'start_row' and downward. This can be useful if only the last line is modified; or in an 'insert' operation: only the last line need be refreshed. :rtype: str """ term = getterminal() return u''.join( [term.normal] + [ self.refresh_row(row) for row in range(start_row, len(self.visible_content)) ] + [term.normal])
[docs] def update(self, ucs): """ Update content buffer with newline-delimited text. :rtype: str """ self.content = ucs return self.refresh()
@property def content(self): """ Content of pager. Return value is "pipe encoded" by :func:`encode_pipe`. :rtype: str """ return encode_pipe('\r\n'.join(self._content)) @content.setter def content(self, ucs_value): # pylint: disable=C0111 # Missing method docstring self._content = self._content_wrap(decode_pipe(ucs_value)) def _content_wrap(self, ucs): """ Return word-wrapped text ``ucs`` that contains newlines. """ term = getterminal() lines = [] for line in ucs.splitlines(): if line.strip(): lines.extend(term.wrap(line, self.visible_width - 1)) else: lines.append(u'') return lines
[docs] def append(self, ucs): """ Update content buffer with additional line(s) of text. "pipe codes" in ``ucs`` are decoded by :func:`decode_pipe`. :param str ucs: unicode string to append-to content buffer. :rtype str :return: terminal sequence suitable for refreshing window. """ self._content.extend(self._content_wrap(decode_pipe(ucs))) return self.move_end() or self.refresh(self.bottom)