memory_analyzer/frontend/memanz_curses.py (100 lines of code) (raw):

#!/usr/bin/env python3 # Copyright (c) Facebook, Inc. and its affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. """ This can either be run standalone with an already generated file (so the user does not have to run the memory analyzer multiple times), or it can be launched directly by the memory analyzer. """ import curses from collections import namedtuple UP_KEYS = [curses.KEY_UP, ord("w"), ord("k"), ord("H"), curses.KEY_PPAGE] DOWN_KEYS = [curses.KEY_DOWN, ord("s"), ord("j"), ord("G"), curses.KEY_NPAGE] RIGHT_KEYS = [curses.KEY_RIGHT, ord("d"), ord("l")] LEFT_KEYS = [curses.KEY_LEFT, ord("a"), ord("h")] class Window: UP = -1 DOWN = 1 def __init__(self, stdscr, pages, titles): self.top = 0 self.position = self.top self.pages = pages self.page_titles = titles self.cur_page = 0 self.window = stdscr self.height = curses.LINES - 1 self.width = curses.COLS - 1 self.bottom = len(self.cur_page.items) curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE) @property def cur_page(self): return self._cur_page @cur_page.setter def cur_page(self, pos): page = namedtuple("Page", "pos items title") self._cur_page = page( pos=pos, items=self.pages[pos], title=self.page_titles[pos] ) return self._cur_page def status_bar_render(self): statusbarstr = ( f"{self.cur_page.title} | Navigate with arrows or wasd | Press 'q' to exit" ) self.window.attron(curses.color_pair(1)) self.window.addstr(self.height, 0, statusbarstr) self.window.addstr( self.height, len(statusbarstr), " " * (self.width - len(statusbarstr)) ) self.window.attroff(curses.color_pair(1)) def run(self): self.display() self.user_input() def display(self): self.window.erase() for idx, item in enumerate( self.cur_page.items[self.position : self.position + self.height - 1] ): self.window.addstr(idx, 0, item) self.status_bar_render() self.window.refresh() def scroll_up(self, user_select): if ( user_select in [curses.KEY_UP, ord("w"), ord("k")] and self.position != self.top ): self.position += self.UP elif user_select == ord("H") and self.position != self.top: self.position = self.top elif user_select == curses.KEY_PPAGE and self.position != self.top: if self.position - self.height > self.top: self.position = self.position - self.height else: self.position = self.top def scroll_down(self, user_select): if ( user_select in [curses.KEY_DOWN, ord("s"), ord("j")] and self.position + self.height <= self.bottom ): self.position += self.DOWN elif user_select == ord("G") and self.position + self.height != self.bottom: self.position = self.bottom - self.height + 1 elif user_select == curses.KEY_NPAGE: if self.position + self.height < self.bottom - self.height: self.position = self.position + self.height else: self.position = self.bottom - self.height + 1 def scroll_right(self, user_select): if len(self.pages) != self.cur_page.pos + 1: self.cur_page = self.cur_page.pos + 1 self.bottom = len(self.cur_page.items) def scroll_left(self, user_select): if self.cur_page.pos != 0: self.cur_page = self.cur_page.pos - 1 self.bottom = len(self.cur_page.items) def user_input(self): user_select = self.window.getch() while user_select != ord("q"): self.status_bar_render() if self.bottom > self.height: if user_select in UP_KEYS: self.scroll_up(user_select) elif user_select in DOWN_KEYS: self.scroll_down(user_select) if user_select in RIGHT_KEYS: self.scroll_right(user_select) elif user_select in LEFT_KEYS: self.scroll_left(user_select) self.display() user_select = self.window.getch()