Complete rewrite and removed legacy bash version
This commit is contained in:
233
src/libs/PyInquirer/prompts/checkbox.py
Normal file
233
src/libs/PyInquirer/prompts/checkbox.py
Normal file
@@ -0,0 +1,233 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
`checkbox` type question
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals
|
||||
from libs.prompt_toolkit.application import Application
|
||||
from libs.prompt_toolkit.key_binding.manager import KeyBindingManager
|
||||
from libs.prompt_toolkit.keys import Keys
|
||||
from libs.prompt_toolkit.layout.containers import Window
|
||||
from libs.prompt_toolkit.filters import IsDone
|
||||
from libs.prompt_toolkit.layout.controls import TokenListControl
|
||||
from libs.prompt_toolkit.layout.containers import ConditionalContainer, \
|
||||
ScrollOffsets, HSplit
|
||||
from libs.prompt_toolkit.layout.dimension import LayoutDimension as D
|
||||
from libs.prompt_toolkit.token import Token
|
||||
|
||||
from .. import PromptParameterException
|
||||
from ..separator import Separator
|
||||
from .common import setup_simple_validator, default_style, if_mousedown
|
||||
|
||||
|
||||
# custom control based on TokenListControl
|
||||
|
||||
|
||||
class InquirerControl(TokenListControl):
|
||||
def __init__(self, choices, **kwargs):
|
||||
self.pointer_index = 0
|
||||
self.selected_options = [] # list of names
|
||||
self.answered = False
|
||||
self._init_choices(choices)
|
||||
super(InquirerControl, self).__init__(self._get_choice_tokens,
|
||||
**kwargs)
|
||||
|
||||
def _init_choices(self, choices):
|
||||
# helper to convert from question format to internal format
|
||||
self.choices = [] # list (name, value)
|
||||
searching_first_choice = True
|
||||
for i, c in enumerate(choices):
|
||||
if isinstance(c, Separator):
|
||||
self.choices.append(c)
|
||||
else:
|
||||
name = c['name']
|
||||
value = c.get('value', name)
|
||||
disabled = c.get('disabled', None)
|
||||
if 'checked' in c and c['checked'] and not disabled:
|
||||
self.selected_options.append(c['name'])
|
||||
self.choices.append((name, value, disabled))
|
||||
if searching_first_choice and not disabled: # find the first (available) choice
|
||||
self.pointer_index = i
|
||||
searching_first_choice = False
|
||||
|
||||
@property
|
||||
def choice_count(self):
|
||||
return len(self.choices)
|
||||
|
||||
def _get_choice_tokens(self, cli):
|
||||
tokens = []
|
||||
T = Token
|
||||
|
||||
def append(index, line):
|
||||
if isinstance(line, Separator):
|
||||
tokens.append((T.Separator, ' %s\n' % line))
|
||||
else:
|
||||
line_name = line[0]
|
||||
line_value = line[1]
|
||||
selected = (line_value in self.selected_options) # use value to check if option has been selected
|
||||
pointed_at = (index == self.pointer_index)
|
||||
|
||||
@if_mousedown
|
||||
def select_item(cli, mouse_event):
|
||||
# bind option with this index to mouse event
|
||||
if line_value in self.selected_options:
|
||||
self.selected_options.remove(line_value)
|
||||
else:
|
||||
self.selected_options.append(line_value)
|
||||
|
||||
if pointed_at:
|
||||
tokens.append((T.Pointer, ' \u276f', select_item)) # ' >'
|
||||
else:
|
||||
tokens.append((T, ' ', select_item))
|
||||
# 'o ' - FISHEYE
|
||||
if choice[2]: # disabled
|
||||
tokens.append((T, '- %s (%s)' % (choice[0], choice[2])))
|
||||
else:
|
||||
if selected:
|
||||
tokens.append((T.Selected, '\u25cf ', select_item))
|
||||
else:
|
||||
tokens.append((T, '\u25cb ', select_item))
|
||||
|
||||
if pointed_at:
|
||||
tokens.append((Token.SetCursorPosition, ''))
|
||||
|
||||
tokens.append((T, line_name, select_item))
|
||||
tokens.append((T, '\n'))
|
||||
|
||||
# prepare the select choices
|
||||
for i, choice in enumerate(self.choices):
|
||||
append(i, choice)
|
||||
tokens.pop() # Remove last newline.
|
||||
return tokens
|
||||
|
||||
def get_selected_values(self):
|
||||
# get values not labels
|
||||
return [c[1] for c in self.choices if not isinstance(c, Separator) and
|
||||
c[1] in self.selected_options]
|
||||
|
||||
@property
|
||||
def line_count(self):
|
||||
return len(self.choices)
|
||||
|
||||
|
||||
def question(message, **kwargs):
|
||||
# TODO add bottom-bar (Move up and down to reveal more choices)
|
||||
# TODO extract common parts for list, checkbox, rawlist, expand
|
||||
# TODO validate
|
||||
if not 'choices' in kwargs:
|
||||
raise PromptParameterException('choices')
|
||||
# this does not implement default, use checked...
|
||||
if 'default' in kwargs:
|
||||
raise ValueError('Checkbox does not implement \'default\' '
|
||||
'use \'checked\':True\' in choice!')
|
||||
|
||||
choices = kwargs.pop('choices', None)
|
||||
validator = setup_simple_validator(kwargs)
|
||||
|
||||
# TODO style defaults on detail level
|
||||
style = kwargs.pop('style', default_style)
|
||||
|
||||
ic = InquirerControl(choices)
|
||||
qmark = kwargs.pop('qmark', '?')
|
||||
|
||||
def get_prompt_tokens(cli):
|
||||
tokens = []
|
||||
|
||||
tokens.append((Token.QuestionMark, qmark))
|
||||
tokens.append((Token.Question, ' %s ' % message))
|
||||
if ic.answered:
|
||||
nbr_selected = len(ic.selected_options)
|
||||
if nbr_selected == 0:
|
||||
tokens.append((Token.Answer, ' done'))
|
||||
elif nbr_selected == 1:
|
||||
tokens.append((Token.Answer, ' [%s]' % ic.selected_options[0]))
|
||||
else:
|
||||
tokens.append((Token.Answer,
|
||||
' done (%d selections)' % nbr_selected))
|
||||
else:
|
||||
tokens.append((Token.Instruction,
|
||||
' (<up>, <down> to move, <space> to select, <a> '
|
||||
'to toggle, <i> to invert)'))
|
||||
return tokens
|
||||
|
||||
# assemble layout
|
||||
layout = HSplit([
|
||||
Window(height=D.exact(1),
|
||||
content=TokenListControl(get_prompt_tokens, align_center=False)
|
||||
),
|
||||
ConditionalContainer(
|
||||
Window(
|
||||
ic,
|
||||
width=D.exact(43),
|
||||
height=D(min=3),
|
||||
scroll_offsets=ScrollOffsets(top=1, bottom=1)
|
||||
),
|
||||
filter=~IsDone()
|
||||
)
|
||||
])
|
||||
|
||||
# key bindings
|
||||
manager = KeyBindingManager.for_prompt()
|
||||
|
||||
@manager.registry.add_binding(Keys.ControlQ, eager=True)
|
||||
@manager.registry.add_binding(Keys.ControlC, eager=True)
|
||||
def _(event):
|
||||
raise KeyboardInterrupt()
|
||||
# event.cli.set_return_value(None)
|
||||
|
||||
@manager.registry.add_binding(' ', eager=True)
|
||||
def toggle(event):
|
||||
pointed_choice = ic.choices[ic.pointer_index][1] # value
|
||||
if pointed_choice in ic.selected_options:
|
||||
ic.selected_options.remove(pointed_choice)
|
||||
else:
|
||||
ic.selected_options.append(pointed_choice)
|
||||
|
||||
@manager.registry.add_binding('i', eager=True)
|
||||
def invert(event):
|
||||
inverted_selection = [c[1] for c in ic.choices if
|
||||
not isinstance(c, Separator) and
|
||||
c[1] not in ic.selected_options and
|
||||
not c[2]]
|
||||
ic.selected_options = inverted_selection
|
||||
|
||||
@manager.registry.add_binding('a', eager=True)
|
||||
def all(event):
|
||||
all_selected = True # all choices have been selected
|
||||
for c in ic.choices:
|
||||
if not isinstance(c, Separator) and c[1] not in ic.selected_options and not c[2]:
|
||||
# add missing ones
|
||||
ic.selected_options.append(c[1])
|
||||
all_selected = False
|
||||
if all_selected:
|
||||
ic.selected_options = []
|
||||
|
||||
@manager.registry.add_binding(Keys.Down, eager=True)
|
||||
def move_cursor_down(event):
|
||||
def _next():
|
||||
ic.pointer_index = ((ic.pointer_index + 1) % ic.line_count)
|
||||
_next()
|
||||
while isinstance(ic.choices[ic.pointer_index], Separator) or \
|
||||
ic.choices[ic.pointer_index][2]:
|
||||
_next()
|
||||
|
||||
@manager.registry.add_binding(Keys.Up, eager=True)
|
||||
def move_cursor_up(event):
|
||||
def _prev():
|
||||
ic.pointer_index = ((ic.pointer_index - 1) % ic.line_count)
|
||||
_prev()
|
||||
while isinstance(ic.choices[ic.pointer_index], Separator) or \
|
||||
ic.choices[ic.pointer_index][2]:
|
||||
_prev()
|
||||
|
||||
@manager.registry.add_binding(Keys.Enter, eager=True)
|
||||
def set_answer(event):
|
||||
ic.answered = True
|
||||
# TODO use validator
|
||||
event.cli.set_return_value(ic.get_selected_values())
|
||||
|
||||
return Application(
|
||||
layout=layout,
|
||||
key_bindings_registry=manager.registry,
|
||||
mouse_support=True,
|
||||
style=style
|
||||
)
|
||||
Reference in New Issue
Block a user