brintos

brintos / llvm-project-archived public Read only

0
0
Text · 10.0 KiB · 06ffc4f Raw
334 lines · python
1##===-- cui.py -----------------------------------------------*- Python -*-===##2##3# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4# See https://llvm.org/LICENSE.txt for license information.5# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6##7##===----------------------------------------------------------------------===##8 9import curses10import curses.ascii11import threading12 13 14class CursesWin(object):15    def __init__(self, x, y, w, h):16        self.win = curses.newwin(h, w, y, x)17        self.focus = False18 19    def setFocus(self, focus):20        self.focus = focus21 22    def getFocus(self):23        return self.focus24 25    def canFocus(self):26        return True27 28    def handleEvent(self, event):29        return30 31    def draw(self):32        return33 34 35class TextWin(CursesWin):36    def __init__(self, x, y, w):37        super(TextWin, self).__init__(x, y, w, 1)38        self.win.bkgd(curses.color_pair(1))39        self.text = ""40        self.reverse = False41 42    def canFocus(self):43        return False44 45    def draw(self):46        w = self.win.getmaxyx()[1]47        text = self.text48        if len(text) > w:49            # trunc_length = len(text) - w50            text = text[-w + 1 :]51        if self.reverse:52            self.win.addstr(0, 0, text, curses.A_REVERSE)53        else:54            self.win.addstr(0, 0, text)55        self.win.noutrefresh()56 57    def setReverse(self, reverse):58        self.reverse = reverse59 60    def setText(self, text):61        self.text = text62 63 64class TitledWin(CursesWin):65    def __init__(self, x, y, w, h, title):66        super(TitledWin, self).__init__(x, y + 1, w, h - 1)67        self.title = title68        self.title_win = TextWin(x, y, w)69        self.title_win.setText(title)70        self.draw()71 72    def setTitle(self, title):73        self.title_win.setText(title)74 75    def draw(self):76        self.title_win.setReverse(self.getFocus())77        self.title_win.draw()78        self.win.noutrefresh()79 80 81class ListWin(CursesWin):82    def __init__(self, x, y, w, h):83        super(ListWin, self).__init__(x, y, w, h)84        self.items = []85        self.selected = 086        self.first_drawn = 087        self.win.leaveok(True)88 89    def draw(self):90        if len(self.items) == 0:91            self.win.erase()92            return93 94        h, w = self.win.getmaxyx()95 96        allLines = []97        firstSelected = -198        lastSelected = -199        for i, item in enumerate(self.items):100            lines = self.items[i].split("\n")101            lines = lines if lines[len(lines) - 1] != "" else lines[:-1]102            if len(lines) == 0:103                lines = [""]104 105            if i == self.getSelected():106                firstSelected = len(allLines)107            allLines.extend(lines)108            if i == self.selected:109                lastSelected = len(allLines) - 1110 111        if firstSelected < self.first_drawn:112            self.first_drawn = firstSelected113        elif lastSelected >= self.first_drawn + h:114            self.first_drawn = lastSelected - h + 1115 116        self.win.erase()117 118        begin = self.first_drawn119        end = begin + h120 121        y = 0122        for i, line in list(enumerate(allLines))[begin:end]:123            attr = curses.A_NORMAL124            if i >= firstSelected and i <= lastSelected:125                attr = curses.A_REVERSE126                line = "{0:{width}}".format(line, width=w - 1)127 128            # Ignore the error we get from drawing over the bottom-right char.129            try:130                self.win.addstr(y, 0, line[:w], attr)131            except curses.error:132                pass133            y += 1134        self.win.noutrefresh()135 136    def getSelected(self):137        if self.items:138            return self.selected139        return -1140 141    def setSelected(self, selected):142        self.selected = selected143        if self.selected < 0:144            self.selected = 0145        elif self.selected >= len(self.items):146            self.selected = len(self.items) - 1147 148    def handleEvent(self, event):149        if isinstance(event, int):150            if len(self.items) > 0:151                if event == curses.KEY_UP:152                    self.setSelected(self.selected - 1)153                if event == curses.KEY_DOWN:154                    self.setSelected(self.selected + 1)155                if event == curses.ascii.NL:156                    self.handleSelect(self.selected)157 158    def addItem(self, item):159        self.items.append(item)160 161    def clearItems(self):162        self.items = []163 164    def handleSelect(self, index):165        return166 167 168class InputHandler(threading.Thread):169    def __init__(self, screen, queue):170        super(InputHandler, self).__init__()171        self.screen = screen172        self.queue = queue173 174    def run(self):175        while True:176            c = self.screen.getch()177            self.queue.put(c)178 179 180class CursesUI(object):181    """Responsible for updating the console UI with curses."""182 183    def __init__(self, screen, event_queue):184        self.screen = screen185        self.event_queue = event_queue186 187        curses.start_color()188        curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE)189        curses.init_pair(2, curses.COLOR_YELLOW, curses.COLOR_BLACK)190        curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK)191        self.screen.bkgd(curses.color_pair(1))192        self.screen.clear()193 194        self.input_handler = InputHandler(self.screen, self.event_queue)195        self.input_handler.daemon = True196 197        self.focus = 0198 199        self.screen.refresh()200 201    def focusNext(self):202        self.wins[self.focus].setFocus(False)203        old = self.focus204        while True:205            self.focus += 1206            if self.focus >= len(self.wins):207                self.focus = 0208            if self.wins[self.focus].canFocus():209                break210        self.wins[self.focus].setFocus(True)211 212    def handleEvent(self, event):213        if isinstance(event, int):214            if event == curses.KEY_F3:215                self.focusNext()216 217    def eventLoop(self):218        self.input_handler.start()219        self.wins[self.focus].setFocus(True)220 221        while True:222            self.screen.noutrefresh()223 224            for i, win in enumerate(self.wins):225                if i != self.focus:226                    win.draw()227            # Draw the focused window last so that the cursor shows up.228            if self.wins:229                self.wins[self.focus].draw()230            curses.doupdate()  # redraw the physical screen231 232            event = self.event_queue.get()233 234            for win in self.wins:235                if isinstance(event, int):236                    if win.getFocus() or not win.canFocus():237                        win.handleEvent(event)238                else:239                    win.handleEvent(event)240            self.handleEvent(event)241 242 243class CursesEditLine(object):244    """Embed an 'editline'-compatible prompt inside a CursesWin."""245 246    def __init__(self, win, history, enterCallback, tabCompleteCallback):247        self.win = win248        self.history = history249        self.enterCallback = enterCallback250        self.tabCompleteCallback = tabCompleteCallback251 252        self.prompt = ""253        self.content = ""254        self.index = 0255        self.startx = -1256        self.starty = -1257 258    def draw(self, prompt=None):259        if not prompt:260            prompt = self.prompt261        (h, w) = self.win.getmaxyx()262        if (len(prompt) + len(self.content)) / w + self.starty >= h - 1:263            self.win.scroll(1)264            self.starty -= 1265            if self.starty < 0:266                raise RuntimeError("Input too long; aborting")267        (y, x) = (self.starty, self.startx)268 269        self.win.move(y, x)270        self.win.clrtobot()271        self.win.addstr(y, x, prompt)272        remain = self.content273        self.win.addstr(remain[: w - len(prompt)])274        remain = remain[w - len(prompt) :]275        while remain != "":276            y += 1277            self.win.addstr(y, 0, remain[:w])278            remain = remain[w:]279 280        length = self.index + len(prompt)281        self.win.move(self.starty + length / w, length % w)282 283    def showPrompt(self, y, x, prompt=None):284        self.content = ""285        self.index = 0286        self.startx = x287        self.starty = y288        self.draw(prompt)289 290    def handleEvent(self, event):291        if not isinstance(event, int):292            return  # not handled293        key = event294 295        if self.startx == -1:296            raise RuntimeError("Trying to handle input without prompt")297 298        if key == curses.ascii.NL:299            self.enterCallback(self.content)300        elif key == curses.ascii.TAB:301            self.tabCompleteCallback(self.content)302        elif curses.ascii.isprint(key):303            self.content = (304                self.content[: self.index] + chr(key) + self.content[self.index :]305            )306            self.index += 1307        elif key == curses.KEY_BACKSPACE or key == curses.ascii.BS:308            if self.index > 0:309                self.index -= 1310                self.content = (311                    self.content[: self.index] + self.content[self.index + 1 :]312                )313        elif key == curses.KEY_DC or key == curses.ascii.DEL or key == curses.ascii.EOT:314            self.content = self.content[: self.index] + self.content[self.index + 1 :]315        elif key == curses.ascii.VT:  # CTRL-K316            self.content = self.content[: self.index]317        elif key == curses.KEY_LEFT or key == curses.ascii.STX:  # left or CTRL-B318            if self.index > 0:319                self.index -= 1320        elif key == curses.KEY_RIGHT or key == curses.ascii.ACK:  # right or CTRL-F321            if self.index < len(self.content):322                self.index += 1323        elif key == curses.ascii.SOH:  # CTRL-A324            self.index = 0325        elif key == curses.ascii.ENQ:  # CTRL-E326            self.index = len(self.content)327        elif key == curses.KEY_UP or key == curses.ascii.DLE:  # up or CTRL-P328            self.content = self.history.previous(self.content)329            self.index = len(self.content)330        elif key == curses.KEY_DOWN or key == curses.ascii.SO:  # down or CTRL-N331            self.content = self.history.next()332            self.index = len(self.content)333        self.draw()334