├── README ├── lines.txt └── curses_scrolling.py /README: -------------------------------------------------------------------------------- 1 | Lyle Scott III 2 | lyle@digitalfoo.net 3 | http://digitalfoo.net/projects/Python_curses_Scrolling_Example/ 4 | 5 | This is a *super* basic implementation of a scrollable terminal screen. 6 | 7 | *Currently, this reads line from lines.txt in the getOutputLines function. 8 | 9 | * UP - scroll up 10 | * DOWN - scroll down 11 | * SPACE - select/toggle a line 12 | * ESC - exit 13 | -------------------------------------------------------------------------------- /lines.txt: -------------------------------------------------------------------------------- 1 | BaAcJy3b 2 | Ve6oiMbSLfqmm0jsdVE35qjgPQ5sBgodCvI7 3 | dEsIVNU2 4 | eP4auN7I9jKvFouQIlBThDlkicENl8QPt0h5QUf 5 | ZIROlLG7cAQQ1pdNYKHFIoNs 6 | guKTfp2LGosuMaTgOZCtcL7RQZoU3W 7 | V1QM 8 | xUhF0BEjQ4IsUUVKk0kwKb4 9 | 6xgYORyjneUli0VHUKUNba8MjjPAq5DdvPc7369H 10 | B7wBGtgt 11 | tDq8xDLbDvqPYS3fwrNLbCBZh3yiItm 12 | xBC2qhV 13 | bgY1YvARImTdx08gQhivjhpQpkj8B 14 | hfU27q3jvqeUsai7HBmSpDzPnFffDMvFOCvll 15 | 2OcNGgB8 16 | ZDJaRDacj2VJT9xBCaBJ9Na2O 17 | X0V 18 | xLNN2fnsIhnW6FsxqDeOBpJWDhB7Vsm1rNhENvP 19 | 3wbKtOPSbNxzrxvTuoH5RiqtOfxSHtqJIrxQ 20 | iA52SIY4k 21 | aEwn99DngjvmhwMI45qdAG3rOQzCT 22 | UBvhxeDrZOqwrQ3xNcQnFmRZHwdSdpVZnihJB4 23 | 16yEB2c4LDrnbZF6RdyY3e0FNsDvc4x2NGWpw2 24 | zv0tmdmkY5Gn 25 | G2Up8Cd1nNhcEnRIC4tIicXzFhJ5GD3D6o7i6Hl 26 | zDBM 27 | I0zhx4RUDtRiyOxuJYvGRRYA4UxFLkMGR1s 28 | dzSXh7n3qQndsCRzvHjMD6pIS 29 | 1Kpg 30 | BYcbUKpitx80 31 | D14st 32 | QlQQmLE4aHqVKYL2APjDrGCW1onLm5TEB2 33 | wFMmn 34 | 8fcRZhOgHcaTKQJmddVNUb07mwBl 35 | imTo9NhQWFCAnOw8Q2i 36 | rcyE 37 | TRsngOkhZeoIkUVSVNQH0IfPGLPXwOwo9Ykqi 38 | 6DRJhndNxNCCTr6myDXEYAqNkS 39 | jhoNm9Y8bYBv2ahyJb09Fm8MAS6ylVpx3eOyap 40 | BSz9gtfEKuwNaJGvKbg 41 | 5pPp3RS 42 | o3KPEwXfixQbu4NHkoCkUR18gxZmN8qe 43 | UIOHJO8ximVbh4Wro2bnSEDEkilHOFNSzS 44 | jzccNvLHwhobYvU55twKClFt 45 | gVfQzkTgHCsMkLEbpupUXVrCkjBo 46 | XLiMkdv4h0zP6FDjgdM 47 | I3AfSpUHzuu0eUbHXcSo 48 | lz2NUeQWq7ogumTo8OOySToIQr3fpwWyS5GPzir 49 | k5TQPl4pLJKFw6N 50 | TOCjjClYSjANFGz69uLoW0mt7pJRX8G 51 | xcU 52 | Jqp 53 | T61KKshSq 54 | cBCjVTqF3NOJgeXjZb9tSzPakeOJRoYwfbB 55 | cAgiktPeAXpTAZ5 56 | n582kYl9keMyJZ 57 | FLQjXRSXulEvf3u2hz28WLddRZUcv8HUmiR 58 | G303fSTJTJyazH6plrEkWT82bGKKleJl0We4Q 59 | iM4Q21QmnaPdTL8yrpPNlUIDaR40Ej 60 | eKPacJ6CereCIk 61 | sVB3aXIzI8m3kbpQcUJao9MTSdmA9k2H 62 | tO73NUdJJ3T3dBaWpt 63 | BFIIDQRT2yYrV8jpyNqSmo9c1Uk5Qf647dTtur 64 | SvyhUh9kGX 65 | pbSX8qZkHmZfCcO9o0lEoKdhFe 66 | WJEEtf1kIl0FCciNktPftiLTb 67 | MVQrJh0Ilq8JjJ7ssbVA7dMV 68 | jW6sW1n1spfU4XI4iMwGYE116BqmDjstm9izQ 69 | 9ewEA2S4NtE3I260lAVCQt98UD 70 | 2r7WaPdIdVBaC4Q1ETn 71 | VDzC1 72 | KbxlzJ70O4FRytsU 73 | FKB8CrDksKnUEzswgnItginJ0Og1rdn8 74 | JclFu 75 | PLQqCpnsPKPTxmd4FzXkyQ 76 | sjjeZESqzTqZmpzp 77 | 244NAenB8V4QIkNjmulBz3MF7MNxRoS8oI 78 | ZfA1NZ3bKUkZa3UMrxANLusZNPvlXtLQtmlBfq 79 | caxki7wXE4BDIhCEWXoNS 80 | 1N69bVxKSZ1a1UReo08fayz7kdCwNJfkypk8UGf 81 | SbdKxUOt8V7hD1tmUornUXB1R0 82 | dpGdOMq 83 | Rg7RV60thLAnMGPTasIph2kcFub9oRFbAvFmAaU 84 | eUqbeMUKyAMfLYT3AjOIGoJ 85 | bsorsLAb 86 | ZgW7BNjzkg1JjkJOx6e1xhIpUiQ19d8hKNneXtnR 87 | wxszERqpwC1n05bIbVPwV9k3vmbP1gC 88 | NHNOITH1STXltLhvguNNOlGRmdmIyZu7C 89 | u0E4MOlUaYKoHXnnqwQRhmKBC2sDpQrW0ObANVdQ 90 | w6eW 91 | 9OdqwUegAM2hEMsRCKMscl 92 | RikHBLCqM9UkArKGiFFCv5s942ZB 93 | Zhxuesz6Q6q5wlaFHklL1nri 94 | YNSeFqRAlD 95 | Tcfpaobm3JsfRHsfIc7bzEu9k9O8X 96 | HgsUk7DpO4a0wPDM655Si 97 | Fp8urlcrlg8TMWx4vS7l 98 | wJlEf 99 | Dfr1uVU1fKkDy7rQqUl4S 100 | 9RUS2UkXT5DNFM 101 | -------------------------------------------------------------------------------- /curses_scrolling.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Lyle Scott, III 5 | lyle@digitalfoo.net 6 | 7 | A simple demo that uses curses to scroll the terminal. 8 | """ 9 | import curses 10 | import sys 11 | import random 12 | import time 13 | 14 | 15 | class MenuDemo: 16 | DOWN = 1 17 | UP = -1 18 | SPACE_KEY = 32 19 | ESC_KEY = 27 20 | 21 | PREFIX_SELECTED = '_X_' 22 | PREFIX_DESELECTED = '___' 23 | 24 | outputLines = [] 25 | screen = None 26 | 27 | def __init__(self): 28 | self.screen = curses.initscr() 29 | curses.noecho() 30 | curses.cbreak() 31 | self.screen.keypad(1) 32 | self.screen.border(0) 33 | self.topLineNum = 0 34 | self.highlightLineNum = 0 35 | self.markedLineNums = [] 36 | self.getOutputLines() 37 | self.run() 38 | 39 | def run(self): 40 | while True: 41 | self.displayScreen() 42 | # get user command 43 | c = self.screen.getch() 44 | if c == curses.KEY_UP: 45 | self.updown(self.UP) 46 | elif c == curses.KEY_DOWN: 47 | self.updown(self.DOWN) 48 | elif c == self.SPACE_KEY: 49 | self.markLine() 50 | elif c == self.ESC_KEY: 51 | self.exit() 52 | 53 | def markLine(self): 54 | linenum = self.topLineNum + self.highlightLineNum 55 | if linenum in self.markedLineNums: 56 | self.markedLineNums.remove(linenum) 57 | else: 58 | self.markedLineNums.append(linenum) 59 | 60 | def getOutputLines(self): 61 | ### !!! 62 | ### This is where you would write a function to parse lines into rows 63 | ### and columns. For this demo, I'll just create a bunch of random ints 64 | ### !!! 65 | self.outputLines = [x.strip() for x in open('lines.txt').readlines()] 66 | self.nOutputLines = len(self.outputLines) 67 | 68 | def displayScreen(self): 69 | # clear screen 70 | self.screen.erase() 71 | 72 | # now paint the rows 73 | top = self.topLineNum 74 | bottom = self.topLineNum+curses.LINES 75 | for (index,line,) in enumerate(self.outputLines[top:bottom]): 76 | linenum = self.topLineNum + index 77 | if linenum in self.markedLineNums: 78 | prefix = self.PREFIX_SELECTED 79 | else: 80 | prefix = self.PREFIX_DESELECTED 81 | 82 | line = '%s %s' % (prefix, line,) 83 | 84 | # highlight current line 85 | if index != self.highlightLineNum: 86 | self.screen.addstr(index, 0, line) 87 | else: 88 | self.screen.addstr(index, 0, line, curses.A_BOLD) 89 | self.screen.refresh() 90 | 91 | # move highlight up/down one line 92 | def updown(self, increment): 93 | nextLineNum = self.highlightLineNum + increment 94 | 95 | # paging 96 | if increment == self.UP and self.highlightLineNum == 0 and self.topLineNum != 0: 97 | self.topLineNum += self.UP 98 | return 99 | elif increment == self.DOWN and nextLineNum == curses.LINES and (self.topLineNum+curses.LINES) != self.nOutputLines: 100 | self.topLineNum += self.DOWN 101 | return 102 | 103 | # scroll highlight line 104 | if increment == self.UP and (self.topLineNum != 0 or self.highlightLineNum != 0): 105 | self.highlightLineNum = nextLineNum 106 | elif increment == self.DOWN and (self.topLineNum+self.highlightLineNum+1) != self.nOutputLines and self.highlightLineNum != curses.LINES: 107 | self.highlightLineNum = nextLineNum 108 | 109 | def restoreScreen(self): 110 | curses.initscr() 111 | curses.nocbreak() 112 | curses.echo() 113 | curses.endwin() 114 | 115 | # catch any weird termination situations 116 | def __del__(self): 117 | self.restoreScreen() 118 | 119 | 120 | if __name__ == '__main__': 121 | ih = MenuDemo() 122 | 123 | --------------------------------------------------------------------------------