Shellmen/src/libs/PyInquirer/prompts/list.py

185 lines
6.2 KiB
Python

# -*- coding: utf-8 -*-
"""
`list` type question
"""
from __future__ import print_function
from __future__ import unicode_literals
import sys
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 if_mousedown, default_style
# custom control based on TokenListControl
# docu here:
# https://github.com/jonathanslenders/python-prompt-toolkit/issues/281
# https://github.com/jonathanslenders/python-prompt-toolkit/blob/master/examples/full-screen-layout.py
# https://github.com/jonathanslenders/python-prompt-toolkit/blob/master/docs/pages/full_screen_apps.rst
PY3 = sys.version_info[0] >= 3
if PY3:
basestring = str
class InquirerControl(TokenListControl):
def __init__(self, choices, **kwargs):
self.selected_option_index = 0
self.answered = False
self.choices = choices
self._init_choices(choices)
super(InquirerControl, self).__init__(self._get_choice_tokens,
**kwargs)
def _init_choices(self, choices, default=None):
# helper to convert from question format to internal format
self.choices = [] # list (name, value, disabled)
searching_first_choice = True
for i, c in enumerate(choices):
if isinstance(c, Separator):
self.choices.append((c, None, None))
else:
if isinstance(c, basestring):
self.choices.append((c, c, None))
else:
name = c.get('name')
value = c.get('value', name)
disabled = c.get('disabled', None)
self.choices.append((name, value, disabled))
if searching_first_choice:
self.selected_option_index = i # found the first choice
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, choice):
selected = (index == self.selected_option_index)
@if_mousedown
def select_item(cli, mouse_event):
# bind option with this index to mouse event
self.selected_option_index = index
self.answered = True
cli.set_return_value(None)
tokens.append((T.Pointer if selected else T, ' \u276f ' if selected
else ' '))
if selected:
tokens.append((Token.SetCursorPosition, ''))
if choice[2]: # disabled
tokens.append((T.Selected if selected else T,
'- %s (%s)' % (choice[0], choice[2])))
else:
try:
tokens.append((T.Selected if selected else T, str(choice[0]),
select_item))
except:
tokens.append((T.Selected if selected else T, choice[0],
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_selection(self):
return self.choices[self.selected_option_index]
def question(message, **kwargs):
# TODO disabled, dict choices
if not 'choices' in kwargs:
raise PromptParameterException('choices')
choices = kwargs.pop('choices', None)
default = kwargs.pop('default', 0) # TODO
qmark = kwargs.pop('qmark', '?')
# TODO style defaults on detail level
style = kwargs.pop('style', default_style)
ic = InquirerControl(choices)
def get_prompt_tokens(cli):
tokens = []
tokens.append((Token.QuestionMark, qmark))
tokens.append((Token.Question, ' %s ' % message))
if ic.answered:
tokens.append((Token.Answer, ' ' + ic.get_selection()[0]))
else:
tokens.append((Token.Instruction, ' (Use arrow keys)'))
return tokens
# assemble layout
layout = HSplit([
Window(height=D.exact(1),
content=TokenListControl(get_prompt_tokens)
),
ConditionalContainer(
Window(ic),
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(Keys.Down, eager=True)
def move_cursor_down(event):
def _next():
ic.selected_option_index = (
(ic.selected_option_index + 1) % ic.choice_count)
_next()
while isinstance(ic.choices[ic.selected_option_index][0], Separator) or\
ic.choices[ic.selected_option_index][2]:
_next()
@manager.registry.add_binding(Keys.Up, eager=True)
def move_cursor_up(event):
def _prev():
ic.selected_option_index = (
(ic.selected_option_index - 1) % ic.choice_count)
_prev()
while isinstance(ic.choices[ic.selected_option_index][0], Separator) or \
ic.choices[ic.selected_option_index][2]:
_prev()
@manager.registry.add_binding(Keys.Enter, eager=True)
def set_answer(event):
ic.answered = True
event.cli.set_return_value(ic.get_selection()[1])
return Application(
layout=layout,
key_bindings_registry=manager.registry,
mouse_support=True,
style=style
)