#
# A simple text-UI to select from a list
#
# Main features:
#  - highlights selected line
#  - up/down/page-up/page-down to move
#  - enter selects
#  - type any pattern to dynamically filter the list
#
import curses, re, sys

class JumpMenu:
    def __init__(self):
        pass

    def run(self, items, re_str=""):
        self.items = items
        self.init_re_str = re_str
        return curses.wrapper( self._run_wrapper )

    def _run_wrapper(self, scr):
        self.scr = scr
        re_str = self.init_re_str
        curses.use_default_colors()
        curses.curs_set(0)
        self.scr.keypad(1)
        self.scr.clear()
        maxy,_ = self.scr.getmaxyx()
        self.disp_len = maxy-1

        filt_items = list(self.items)
        line = 0
        self.drawscr(filt_items,re_str,line)
        while True:
            c = self.scr.getch()
            if c == curses.KEY_UP:
                line -= 1
            elif c == curses.KEY_DOWN:
                line += 1
            elif c == curses.KEY_PPAGE:
                line -= self.disp_len
            elif c == curses.KEY_NPAGE:
                line += self.disp_len
            elif c == ord('\n'):
                break
            elif c == curses.KEY_BACKSPACE or c == 127 or c == '\b':
                re_str = re_str[:-1]
                (line,filt_items) = self.update_re_str( re_str, line, filt_items )
            else:
                re_str += chr(c)
                (line,filt_items) = self.update_re_str( re_str, line, filt_items )
            if line < 0:
                line = 0
            if line >= len(filt_items):
                line = len(filt_items) - 1
            self.drawscr( filt_items, re_str, line )

        # filt_items[line] is the current item.
        # Return the position in the full self.items
        return self.items.index( filt_items[line] )

    def drawscr(self, items,re_str,pos):
        self.scr.clear()

        start_pos = int(pos / self.disp_len)*self.disp_len
        for n in range(0, self.disp_len):
            items_idx = start_pos + n
            if items_idx >= 0 and items_idx < len(items):
                if pos == items_idx:
                    text_type = curses.A_REVERSE
                else:
                    text_type = curses.A_NORMAL
                self.scr.addstr(n, 1, items[items_idx], text_type)
        if len(re_str) > 0:
            self.scr.addstr(self.disp_len, 1,
                            "Search string: "+re_str,
                            curses.A_REVERSE)
        self.scr.refresh()

    def filter_items(self,srch_str):
        re_str = '.*' + srch_str.replace(' ','.*')
        return [d for d in self.items if re.match(re_str,d)]

    def update_re_str(self, re_str, line, filt_items):
        if line >= 0 and line < len(filt_items):
            curr_item = filt_items[line]
        else:
            curr_item = None
        filt_items = self.filter_items(re_str)
        try:
            line = filt_items.index(curr_item)
        except:
            line = 0
        return (line,filt_items)

if __name__ == '__main__':
    tmp = JumpMenu()
    result = tmp.run(['%d' % n for n in range(1000)])
    print(result)
