aboutsummaryrefslogtreecommitdiff
blob: df76251d7236f215a3238196a75e5a898e3ff774 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
"""A module to interactively query"""
from pydoc import pager

from curtsies import CursorAwareWindow, Input, fsarray, fmtstr
from curtsies.fmtfuncs import underline
from pbraw import grab


class Position:
    def __init__(self, row=0, column=0):
        self.row = row
        self.column = column

def clamp(x, m, M):
    return max(m, min(x, M))

def render_entry(entry, width, active=False):
    # (name, contents, state, value)
    char = '*' if entry[2] else ' '
    w = 3 + fmtstr(entry[0]).width + 2
    text = fmtstr(entry[3])
    return fmtstr(
            '[' + underline(char) if active else char + '] ' +
            entry[0] + ' ' +
            entry[3][:width - w - 2] + '..' if text.width < width - w else entry[3])

def process_entry(entry):
    if isinstance(entry, str):
        return (entry[0], None, False, None)
    return (entry[0], entry[1], False, entry[0] if entry[0].endswith('.ebuild') else 'files/{}'.format(entry[0]))

class Prompt:
    def __init__(self, entries):
        self.entries = [process_entry(x) for x in entries]
        self.idx = 0
        self.text = ''
        self.list = True
        self.cursor_pos = Position()

    def run(self):
        with open('/dev/tty', 'r') as tty_in, \
             open('/dev/tty', 'w') as tty_out, \
             Input(in_stream=tty_in) as input_, \
             CursorAwareWindow(in_stream=tty_in,
                               out_stream=tty_out,
                               hide_cursor=False,
                               extra_bytes_callback=input_.unget_bytes) as window:
            self.window = window
            self.render()
            for event in input_:
                if self.process_event(event) == -1:
                    break
                self.render()
        return [(x[0], x[1], x[3]) for x in self.entries if x[2]]

    def clamp(self, x):
        return clamp(x, 0, len(self.entries))

    def preview(self):
        entry = self.entries[self.idx]
        if entry[0] is not None:
            pager(entry[1])
        else:
            gr = grab(entry)
            if not gr:
                del self.entries[self.idx]
                self.idx = clamp(self.idx - 1)
                pager('Error: could not fetch '.format(entry))
            self.entries[self.idx:self.idx+1] = [process_entry((x[0], x[1].encode('utf-8'))) for x in gr]
            pager(self.entries[self.idx][1])

    def toggle(self):
        if self.idx == len(self.entries):
            return
        self.entries[self.idx][3] = not self.entries[self.idx][3]

    def process_event(self, event):
        if self.list:
            if event == '<UP>':
                self.idx = clamp(self.idx - 1)
            elif event == '<DOWN>':
                self.idx = clamp(self.idx + 1)
            elif event == '<SPACE>':
                self.toggle(self.idx)
            elif event in {'p', 'P'}:
                self.preview()
            elif event in {'<ESC>', '<Ctrl-g>'}:
                return -1
            elif event in {'<Ctrl-j>', '<Ctrl-m>'}:
                self.list = False
                if self.idx == len(self.entries):
                    return -1
                self.cursor_pos.column = fmtstr(self.entries[self.idx][3]).width
        else:
            if event in {'<ESC>', '<Ctrl-g>'}:
                self.list = True
            if event == '<BACKSPACE>':
                self.delete_last_char()
            elif event == '<SPACE>':
                self.add_char(' ')
            elif event in {'<Ctrl-j>', '<Ctrl-m>'}:
                self.set_text(self.text)
                self.list = True
            elif isinstance(event, str) and not event.startswith('<'):
                self.add_char(event)

    def render(self):
        if self.list:
            output = fsarray([render_entry(x) for x in self.entries] + [' [ OK ] '],
                    self.window.width)
            self.window.render_to_terminal(output)
            return
        cur = self.entries[self.idx]
        output = fsarray(['Please provide value for {}'.format(cur[0]), cur[3]], self.window.width)
        self.window.render_to_terminal(output, (self.cursor_pos.row, self.cursor_pos.column))

    def add_char(self, char):
        self.entries[self.idx][3] += char
        self.cursor_pos.column += 1

    def delete_last_char(self):
        if self.text:
            self.entries[self.idx][3] += self.entries[self.idx][3][:-1]
            self.cursor_pos.column -= 1