# Copyright (C) 2009-2013 Roman Zimbelmann # Copyright (C) 2010 David Barnett # This software is distributed under the terms of the GNU GPL version 3. """The pager displays text and allows you to scroll inside it.""" from . import Widget from ranger.core.loader import CommandLoader from ranger.gui import ansi from ranger.ext.direction import Direction import ranger.ext.img_display as img_display # TODO: Scrolling in embedded pager class Pager(Widget): source = None source_is_stream = False old_source = None old_scroll_begin = 0 old_startx = 0 need_clear_image = False need_redraw_image = False max_width = None def __init__(self, win, embedded=False): Widget.__init__(self, win) self.embedded = embedded self.scroll_begin = 0 self.startx = 0 self.markup = None self.lines = [] self.image = None self.image_drawn = False def open(self): self.scroll_begin = 0 self.markup = None self.max_width = 0 self.startx = 0 self.need_redraw = True def clear_image(self, force=False): if (force or self.need_clear_image) and self.image_drawn: img_display.clear(self.x, self.y, self.wid, self.hei) self.need_clear_image = False self.image_drawn = False def close(self): if self.image: self.need_clear_image = True self.clear_image() if self.source and self.source_is_stream: self.source.close() def destroy(self): self.clear_image(force=True) def finalize(self): self.fm.ui.win.move(self.y, self.x) def draw(self): if self.need_clear_image: self.need_redraw = True if self.old_source != self.source: self.old_source = self.source self.need_redraw = True if self.old_scroll_begin != self.scroll_begin or \ self.old_startx != self.startx: self.old_startx = self.startx self.old_scroll_begin = self.scroll_begin self.need_redraw = True if self.need_redraw: self.win.erase() self.need_redraw_image = True self.clear_image() if not self.image: line_gen = self._generate_lines( starty=self.scroll_begin, startx=self.startx) for line, i in zip(line_gen, range(self.hei)): self._draw_line(i, line) self.need_redraw = False def draw_image(self): if self.image and self.need_redraw_image: self.source = None self.need_redraw_image = False try: cmd = CommandLoader([img_display.W3MIMGDISPLAY_PATH] + img_display.W3MIMGDISPLAY_OPTIONS, input=img_display.generate_w3m_input(self.image, self.x, self.y, self.wid, self.hei), descr="loading preview image", silent=True, kill_on_pause=True) self.fm.loader.add(cmd) self.image_drawn = True except img_display.ImgDisplayUnsupportedException: self.fm.settings.preview_images = False except Exception as e: self.fm.notify(e, bad=True) def _draw_line(self, i, line): if self.markup is None: self.addstr(i, 0, line) elif self.markup == 'ansi': try: self.win.move(i, 0) except: pass else: for chunk in ansi.text_with_fg_bg_attr(line): if isinstance(chunk, tuple): self.set_fg_bg_attr(*chunk) else: self.addstr(chunk) def move(self, narg=None, **kw): direction = Direction(kw) if direction.horizontal(): self.startx = direction.move( direction=direction.right(), override=narg, maximum=self.max_width, current=self.startx, pagesize=self.wid, offset=-self.wid + 1) if direction.vertical(): if self.source_is_stream: self._get_line(self.scroll_begin + self.hei * 2) self.scroll_begin = direction.move( direction=direction.down(), override=narg, maximum=len(self.lines), current=self.scroll_begin, pagesize=self.hei, offset=-self.hei + 1) def press(self, key): self.fm.ui.keymaps.use_keymap('pager') self.fm.ui.press(key) def set_image(self, image): if self.image: self.need_clear_image = True self.image = image if self.source and self.source_is_stream: self.source.close() self.source = None self.source_is_stream = False def set_source(self, source, strip=False): if self.image: self.image = None self.need_clear_image = True if self.source and self.source_is_stream: self.source.close() self.max_width = 0 if isinstance(source, str): self.source_is_stream = False self.lines = source.splitlines() if self.lines: self.max_width = max(len(line) for line in self.lines) elif hasattr(source, '__getitem__'): self.source_is_stream = False self.lines = source if self.lines: self.max_width = max(len(line) for line in source) elif hasattr(source, 'readline'): self.source_is_stream = True self.lines = [] else: self.source = None self.source_is_stream = False return False self.markup = 'ansi' if not self.source_is_stream and strip: self.lines = map(lambda x: x.strip(), self.lines) self.source = source return True def click(self, event): n = event.ctrl() and 1 or 3 direction = event.mouse_wheel_direction() if direction: self.move(down=direction * n) return True def _get_line(self, n, attempt_to_read=True): assert isinstance(n, int), n try: return self.lines[n] except (KeyError, IndexError): if attempt_to_read and self.source_is_stream: try: for l in self.source: if len(l) > self.max_width: self.max_width = len(l) self.lines.append(l) if len(self.lines) > n: break except (UnicodeError, IOError): pass return self._get_line(n, attempt_to_read=False) return "" def _generate_lines(self, starty, startx): i = starty if not self.source: raise StopIteration while True: try: line = self._get_line(i).expandtabs(4) if self.markup is 'ansi': line = ansi.char_slice(line, startx, self.wid) + ansi.reset else: line = line[startx:self.wid + startx] yield line.rstrip() except IndexError: raise StopIteration i += 1