#!/usr/bin/env python3 import curses import threading from .globals import CURSES_LOCK class Pager: def set_dim(self, height, width): if height < 4 or width < 10: raise ValueError("Size too small") with self._lock: if (height, width) != (self._height, self._width): self.win.erase() self.win.noutrefresh() self.win.resize(height, width) self.win.redrawwin() self._height = height self._width = width self._regen_actual() self._redraw() def set_pos(self, y, x): with self._lock: if (y, x) != (self._y, self._x): self.win.mvwin(y, x) self._y = y self._x = x self.win.noutrefresh() def _redraw(self): with self._lock, CURSES_LOCK: start = max(0, self._bottom_a - self._height + 2) end = self._bottom_a todisp = self._actual[start:end] if len(todisp) < self._height-2 and len(todisp) < len(self._actual): end = self._bottom_a+self._height-2-len(todisp) todisp += self._actual[self._bottom_a:end] self.win.erase() self.win.border() if start > 0: self.win.addch(0, self._width//2, '⯅') for n, l in enumerate(todisp, 1): self.win.addstr(n, 1, l) if end < len(self._actual): self.win.addch(self._height-1, self._width//2, '⯆') self.win.refresh() def _split_line(self, l): with self._lock: lines = [] while len(l) >= self._width-2: lines.append(l[:self._width-3] + '…') l = "⮡ "+l[self._width-3:] lines.append(l) return lines def _regen_actual(self): with self._lock: self._actual = [] for n, l in enumerate(self._lines): self._actual += self._split_line(l) if n == self._bottom: self._bottom_a = len(self._actual) self._bottom_a = max(self._bottom_a, self._height-2) def display_many(self, s, split=False): with self._lock: if split and self._lines: self._lines.append("") self._actual.append("") for l in s: self._lines.append(l) self._actual += self._split_line(l) self._bottom = len(self._lines) - 1 self._bottom_a = len(self._actual) self._redraw() def display(self, s): self.display_many((s, )) def user_page(self, refresh=None): curses.curs_set(0) while True: l = self.win.getch() with self._lock: if l == curses.KEY_RESIZE: if refresh is not None: refresh() elif l == ord('q'): break elif l in (ord('k'), curses.KEY_UP): if self._bottom_a > self._height-2: self._bottom_a -= 1 self._redraw() elif l in (ord('j'), curses.KEY_DOWN): if self._bottom_a < len(self._actual): self._bottom_a += 1 self._redraw() elif l == curses.KEY_NPAGE: if self._bottom_a < len(self._actual): self._bottom_a = min(len(self._actual), self._bottom_a + self._height - 2) self._redraw() elif l == curses.KEY_PPAGE: if self._bottom_a > 0: self._bottom_a = max(self._height - 2, self._bottom_a - self._height + 2) self._redraw() elif l == curses.KEY_HOME: if self._bottom_a > self._height - 2: self._bottom_a = self._height - 2 self._redraw() elif l == curses.KEY_END: if self._bottom_a < len(self._actual): self._bottom_a = len(self._actual) self._redraw() curses.curs_set(1) def clear(self): with self._lock: self._lines = [] self._actual = [] self._bottom_a = 0 self._bottom = -1 self._redraw() def __init__(self, y, x, height, width): with CURSES_LOCK: self.win = curses.newwin(height, width, y, x) self.win.leaveok(True) self.win.keypad(True) self._lock = threading.RLock() self._height = height self._width = width self._y = -1 self._x = -1 self._lines = [] self._actual = [] self._bottom = -1 self._bottom_a = 0 self.set_pos(y, x) self.set_dim(height, width)