Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
textpad.py205 linesDownload Raw Back to curses
1"""Simple textbox editing widget with Emacs-like keybindings."""2 3import curses4import curses.ascii5 6def rectangle(win, uly, ulx, lry, lrx):7    """Draw a rectangle with corners at the provided upper-left8    and lower-right coordinates.9    """10    win.vline(uly+1, ulx, curses.ACS_VLINE, lry - uly - 1)11    win.hline(uly, ulx+1, curses.ACS_HLINE, lrx - ulx - 1)12    win.hline(lry, ulx+1, curses.ACS_HLINE, lrx - ulx - 1)13    win.vline(uly+1, lrx, curses.ACS_VLINE, lry - uly - 1)14    win.addch(uly, ulx, curses.ACS_ULCORNER)15    win.addch(uly, lrx, curses.ACS_URCORNER)16    win.addch(lry, lrx, curses.ACS_LRCORNER)17    win.addch(lry, ulx, curses.ACS_LLCORNER)18 19class Textbox:20    """Editing widget using the interior of a window object.21     Supports the following Emacs-like key bindings:22 23    Ctrl-A      Go to left edge of window.24    Ctrl-B      Cursor left, wrapping to previous line if appropriate.25    Ctrl-D      Delete character under cursor.26    Ctrl-E      Go to right edge (stripspaces off) or end of line (stripspaces on).27    Ctrl-F      Cursor right, wrapping to next line when appropriate.28    Ctrl-G      Terminate, returning the window contents.29    Ctrl-H      Delete character backward.30    Ctrl-J      Terminate if the window is 1 line, otherwise insert newline.31    Ctrl-K      If line is blank, delete it, otherwise clear to end of line.32    Ctrl-L      Refresh screen.33    Ctrl-N      Cursor down; move down one line.34    Ctrl-O      Insert a blank line at cursor location.35    Ctrl-P      Cursor up; move up one line.36 37    Move operations do nothing if the cursor is at an edge where the movement38    is not possible.  The following synonyms are supported where possible:39 40    KEY_LEFT = Ctrl-B, KEY_RIGHT = Ctrl-F, KEY_UP = Ctrl-P, KEY_DOWN = Ctrl-N41    KEY_BACKSPACE = Ctrl-h42    """43    def __init__(self, win, insert_mode=False):44        self.win = win45        self.insert_mode = insert_mode46        self._update_max_yx()47        self.stripspaces = 148        self.lastcmd = None49        win.keypad(1)50 51    def _update_max_yx(self):52        maxy, maxx = self.win.getmaxyx()53        self.maxy = maxy - 154        self.maxx = maxx - 155 56    def _end_of_line(self, y):57        """Go to the location of the first blank on the given line,58        returning the index of the last non-blank character."""59        self._update_max_yx()60        last = self.maxx61        while True:62            if curses.ascii.ascii(self.win.inch(y, last)) != curses.ascii.SP:63                last = min(self.maxx, last+1)64                break65            elif last == 0:66                break67            last = last - 168        return last69 70    def _insert_printable_char(self, ch):71        self._update_max_yx()72        (y, x) = self.win.getyx()73        backyx = None74        while y < self.maxy or x < self.maxx:75            if self.insert_mode:76                oldch = self.win.inch()77            # The try-catch ignores the error we trigger from some curses78            # versions by trying to write into the lowest-rightmost spot79            # in the window.80            try:81                self.win.addch(ch)82            except curses.error:83                pass84            if not self.insert_mode or not curses.ascii.isprint(oldch):85                break86            ch = oldch87            (y, x) = self.win.getyx()88            # Remember where to put the cursor back since we are in insert_mode89            if backyx is None:90                backyx = y, x91 92        if backyx is not None:93            self.win.move(*backyx)94 95    def do_command(self, ch):96        "Process a single editing command."97        self._update_max_yx()98        (y, x) = self.win.getyx()99        self.lastcmd = ch100        if curses.ascii.isprint(ch):101            if y < self.maxy or x < self.maxx:102                self._insert_printable_char(ch)103        elif ch == curses.ascii.SOH:                           # ^a104            self.win.move(y, 0)105        elif ch in (curses.ascii.STX,curses.KEY_LEFT,106                    curses.ascii.BS,107                    curses.KEY_BACKSPACE,108                    curses.ascii.DEL):109            if x > 0:110                self.win.move(y, x-1)111            elif y == 0:112                pass113            elif self.stripspaces:114                self.win.move(y-1, self._end_of_line(y-1))115            else:116                self.win.move(y-1, self.maxx)117            if ch in (curses.ascii.BS, curses.KEY_BACKSPACE, curses.ascii.DEL):118                self.win.delch()119        elif ch == curses.ascii.EOT:                           # ^d120            self.win.delch()121        elif ch == curses.ascii.ENQ:                           # ^e122            if self.stripspaces:123                self.win.move(y, self._end_of_line(y))124            else:125                self.win.move(y, self.maxx)126        elif ch in (curses.ascii.ACK, curses.KEY_RIGHT):       # ^f127            if x < self.maxx:128                self.win.move(y, x+1)129            elif y == self.maxy:130                pass131            else:132                self.win.move(y+1, 0)133        elif ch == curses.ascii.BEL:                           # ^g134            return 0135        elif ch == curses.ascii.NL:                            # ^j136            if self.maxy == 0:137                return 0138            elif y < self.maxy:139                self.win.move(y+1, 0)140        elif ch == curses.ascii.VT:                            # ^k141            if x == 0 and self._end_of_line(y) == 0:142                self.win.deleteln()143            else:144                # first undo the effect of self._end_of_line145                self.win.move(y, x)146                self.win.clrtoeol()147        elif ch == curses.ascii.FF:                            # ^l148            self.win.refresh()149        elif ch in (curses.ascii.SO, curses.KEY_DOWN):         # ^n150            if y < self.maxy:151                self.win.move(y+1, x)152                if x > self._end_of_line(y+1):153                    self.win.move(y+1, self._end_of_line(y+1))154        elif ch == curses.ascii.SI:                            # ^o155            self.win.insertln()156        elif ch in (curses.ascii.DLE, curses.KEY_UP):          # ^p157            if y > 0:158                self.win.move(y-1, x)159                if x > self._end_of_line(y-1):160                    self.win.move(y-1, self._end_of_line(y-1))161        return 1162 163    def gather(self):164        "Collect and return the contents of the window."165        result = ""166        self._update_max_yx()167        for y in range(self.maxy+1):168            self.win.move(y, 0)169            stop = self._end_of_line(y)170            if stop == 0 and self.stripspaces:171                continue172            for x in range(self.maxx+1):173                if self.stripspaces and x > stop:174                    break175                result = result + chr(curses.ascii.ascii(self.win.inch(y, x)))176            if self.maxy > 0:177                result = result + "\n"178        return result179 180    def edit(self, validate=None):181        "Edit in the widget window and collect the results."182        while 1:183            ch = self.win.getch()184            if validate:185                ch = validate(ch)186            if not ch:187                continue188            if not self.do_command(ch):189                break190            self.win.refresh()191        return self.gather()192 193if __name__ == '__main__':194    def test_editbox(stdscr):195        ncols, nlines = 9, 4196        uly, ulx = 15, 20197        stdscr.addstr(uly-2, ulx, "Use Ctrl-G to end editing.")198        win = curses.newwin(nlines, ncols, uly, ulx)199        rectangle(stdscr, uly-1, ulx-1, uly + nlines, ulx + ncols)200        stdscr.refresh()201        return Textbox(win).edit()202 203    str = curses.wrapper(test_editbox)204    print('Contents of text box:', repr(str))205 
codekingpro/portable-devtools · Team Ai