# -*- 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 )