Beispiel #1
0
    def __init__(self, pymux):
        self.pymux = pymux

        def get_search_state(cli):
            " Return the currently active SearchState. (The one for the focussed pane.) "
            return pymux.arrangement.get_active_pane(cli).search_state

        # Start from this KeyBindingManager from prompt_toolkit, to have basic
        # editing functionality for the command line. These key binding are
        # however only active when the following `enable_all` condition is met.
        self.registry = MergedRegistry([
            ConditionalRegistry(
                registry=load_key_bindings(
                    enable_auto_suggest_bindings=True,
                    enable_search=
                    False,  # We have our own search bindings, that support multiple panes.
                    enable_extra_page_navigation=True,
                    get_search_state=get_search_state),
                filter=(HasFocus(COMMAND) | HasFocus(PROMPT)
                        | InScrollBuffer(pymux)) & ~HasPrefix(pymux),
            ),
            load_mouse_bindings(),
            self._load_builtins(),
            _load_search_bindings(pymux),
        ])

        self._prefix = (Keys.ControlB, )
        self._prefix_binding = None

        # Load initial bindings.
        self._load_prefix_binding()

        # Custom user configured key bindings.
        # { (needs_prefix, key) -> (command, handler) }
        self.custom_bindings = {}
Beispiel #2
0
    def __init__(
            self,
            registry=None,  # XXX: not used anymore.
            enable_vi_mode=None,  # (`enable_vi_mode` is deprecated.)
            enable_all=True,  #
            get_search_state=None,
            enable_abort_and_exit_bindings=False,
            enable_system_bindings=False,
            enable_search=False,
            enable_open_in_editor=False,
            enable_extra_page_navigation=False,
            enable_auto_suggest_bindings=False):

        assert registry is None or isinstance(registry, Registry)
        assert get_search_state is None or callable(get_search_state)
        enable_all = to_cli_filter(enable_all)

        defaults = load_key_bindings(
            get_search_state=get_search_state,
            enable_abort_and_exit_bindings=enable_abort_and_exit_bindings,
            enable_system_bindings=enable_system_bindings,
            enable_search=enable_search,
            enable_open_in_editor=enable_open_in_editor,
            enable_extra_page_navigation=enable_extra_page_navigation,
            enable_auto_suggest_bindings=enable_auto_suggest_bindings)

        # Note, we wrap this whole thing again in a MergedRegistry, because we
        # don't want the `enable_all` settings to apply on items that were
        # added to the registry as a whole.
        self.registry = MergedRegistry(
            [ConditionalRegistry(defaults, enable_all)])
Beispiel #3
0
    def __init__(self, pymux):
        self.pymux = pymux

        def get_search_state(cli):
            " Return the currently active SearchState. (The one for the focussed pane.) "
            return pymux.arrangement.get_active_pane(cli).search_state

        # Start from this KeyBindingManager from prompt_toolkit, to have basic
        # editing functionality for the command line. These key binding are
        # however only active when the following `enable_all` condition is met.
        self.registry = MergedRegistry([
            ConditionalRegistry(
                registry=load_key_bindings(
                    enable_auto_suggest_bindings=True,
                    enable_search=False,  # We have our own search bindings, that support multiple panes.
                    enable_extra_page_navigation=True,
                    get_search_state=get_search_state),
                filter=(HasFocus(COMMAND) | HasFocus(PROMPT) |
                        InScrollBuffer(pymux)) & ~HasPrefix(pymux),
            ),
            load_mouse_bindings(),
            self._load_builtins(),
            _load_search_bindings(pymux),
        ])

        self._prefix = (Keys.ControlB, )
        self._prefix_binding = None

        # Load initial bindings.
        self._load_prefix_binding()

        # Custom user configured key bindings.
        # { (needs_prefix, key) -> (command, handler) }
        self.custom_bindings = {}
Beispiel #4
0
def _load_key_bindings(*registries):
    registry = MergedRegistry([
        load_abort_and_exit_bindings(),
        load_basic_system_bindings(),
        load_mouse_bindings(),
        load_cpr_bindings(),
    ] + list(registries))
    return registry
Beispiel #5
0
def get_key_bindings(tosh):
    global_registry = KeyBindingManager.for_prompt(enable_all=Condition(_in_prompt_tab)).registry

    mouse_registry = ConditionalRegistry(load_mouse_bindings(), Condition(_in_interactive_tab))
    prompt_registry = ConditionalRegistry(filter=Condition(_in_prompt_tab))
    interactive_registry = ConditionalRegistry(filter=Condition(_in_interactive_tab))

    @global_registry.add_binding(Keys.BracketedPaste)
    def _paste(event):
        tosh.window.active_tab().paste(event)

    @prompt_registry.add_binding(Keys.ControlW)
    @interactive_registry.add_binding(Keys.ControlB, 'w')
    def _close_tab(event):
        tosh.window.active_tab().close()

    @prompt_registry.add_binding(Keys.ControlT)
    @interactive_registry.add_binding(Keys.ControlB, 't')
    def _new_prompt_tab(event):
        from .tosh_tab import ToshTab
        tosh.window.add_tab(ToshTab(tosh))

    @interactive_registry.add_binding(Keys.Any)
    def _forward_to_session(event):
        tosh.window.active_tab().write_to_ssh(prompt_toolkit_key_to_vt100_key(event.key_sequence[0].key, True))

    @global_registry.add_binding(Keys.ControlB, '1')
    @global_registry.add_binding(Keys.ControlB, '2')
    @global_registry.add_binding(Keys.ControlB, '3')
    @global_registry.add_binding(Keys.ControlB, '4')
    @global_registry.add_binding(Keys.ControlB, '5')
    @global_registry.add_binding(Keys.ControlB, '6')
    @global_registry.add_binding(Keys.ControlB, '7')
    @global_registry.add_binding(Keys.ControlB, '8')
    @global_registry.add_binding(Keys.ControlB, '9')
    def _change_tab(event):
        tosh.window.switch_tab(int(event.key_sequence[-1].key) - 1)

    @global_registry.add_binding(Keys.ControlB, Keys.Left)
    def _prev_tab(_):
        tosh.window.prev_tab()

    @global_registry.add_binding(Keys.ControlB, Keys.Right)
    def _next_tab(_):
        tosh.window.next_tab()

    return MergedRegistry([global_registry, prompt_registry, interactive_registry, mouse_registry])
Beispiel #6
0
def load_key_bindings(get_search_state=None,
                      enable_abort_and_exit_bindings=False,
                      enable_system_bindings=False,
                      enable_search=False,
                      enable_open_in_editor=False,
                      enable_extra_page_navigation=False,
                      enable_auto_suggest_bindings=False):
    """
    Create a Registry object that contains the default key bindings.

    :param enable_abort_and_exit_bindings: Filter to enable Ctrl-C and Ctrl-D.
    :param enable_system_bindings: Filter to enable the system bindings (meta-!
            prompt and Control-Z suspension.)
    :param enable_search: Filter to enable the search bindings.
    :param enable_open_in_editor: Filter to enable open-in-editor.
    :param enable_open_in_editor: Filter to enable open-in-editor.
    :param enable_extra_page_navigation: Filter for enabling extra page
        navigation. (Bindings for up/down scrolling through long pages, like in
        Emacs or Vi.)
    :param enable_auto_suggest_bindings: Filter to enable fish-style suggestions.
    """

    assert get_search_state is None or callable(get_search_state)

    # Accept both Filters and booleans as input.
    enable_abort_and_exit_bindings = to_cli_filter(
        enable_abort_and_exit_bindings)
    enable_system_bindings = to_cli_filter(enable_system_bindings)
    enable_search = to_cli_filter(enable_search)
    enable_open_in_editor = to_cli_filter(enable_open_in_editor)
    enable_extra_page_navigation = to_cli_filter(enable_extra_page_navigation)
    enable_auto_suggest_bindings = to_cli_filter(enable_auto_suggest_bindings)

    registry = MergedRegistry([
        # Load basic bindings.
        load_basic_bindings(),
        load_mouse_bindings(),
        ConditionalRegistry(load_abort_and_exit_bindings(),
                            enable_abort_and_exit_bindings),
        ConditionalRegistry(load_basic_system_bindings(),
                            enable_system_bindings),

        # Load emacs bindings.
        load_emacs_bindings(),
        ConditionalRegistry(load_emacs_open_in_editor_bindings(),
                            enable_open_in_editor),
        ConditionalRegistry(
            load_emacs_search_bindings(get_search_state=get_search_state),
            enable_search),
        ConditionalRegistry(load_emacs_system_bindings(),
                            enable_system_bindings),
        ConditionalRegistry(load_extra_emacs_page_navigation_bindings(),
                            enable_extra_page_navigation),

        # Load Vi bindings.
        load_vi_bindings(get_search_state=get_search_state),
        ConditionalRegistry(load_vi_open_in_editor_bindings(),
                            enable_open_in_editor),
        ConditionalRegistry(
            load_vi_search_bindings(get_search_state=get_search_state),
            enable_search),
        ConditionalRegistry(load_vi_system_bindings(), enable_system_bindings),
        ConditionalRegistry(load_extra_vi_page_navigation_bindings(),
                            enable_extra_page_navigation),

        # Suggestion bindings.
        # (This has to come at the end, because the Vi bindings also have an
        # implementation for the "right arrow", but we really want the
        # suggestion binding when a suggestion is available.)
        ConditionalRegistry(load_auto_suggestion_bindings(),
                            enable_auto_suggest_bindings),
    ])

    return registry
Beispiel #7
0
    def __init__(
            self,
            get_globals=None,
            get_locals=None,
            history_filename=None,
            vi_mode=False,

            # For internal use.
            _completer=None,
            _validator=None,
            _lexer=None,
            _extra_buffers=None,
            _extra_buffer_processors=None,
            _on_start=None,
            _extra_layout_body=None,
            _extra_toolbars=None,
            _input_buffer_height=None,
            _accept_action=AcceptAction.RETURN_DOCUMENT,
            _on_exit=AbortAction.RAISE_EXCEPTION):

        self.get_globals = get_globals or (lambda: {})
        self.get_locals = get_locals or self.get_globals

        self._completer = _completer or PythonCompleter(
            self.get_globals, self.get_locals)
        self._validator = _validator or PythonValidator(
            self.get_compiler_flags)
        self.history = FileHistory(
            history_filename) if history_filename else InMemoryHistory()
        self._lexer = _lexer or PygmentsLexer(PythonLexer)
        self._extra_buffers = _extra_buffers
        self._accept_action = _accept_action
        self._on_exit = _on_exit
        self._on_start = _on_start

        self._input_buffer_height = _input_buffer_height
        self._extra_layout_body = _extra_layout_body or []
        self._extra_toolbars = _extra_toolbars or []
        self._extra_buffer_processors = _extra_buffer_processors or []

        # Settings.
        self.show_signature = False
        self.show_docstring = False
        self.show_meta_enter_message = True
        self.completion_visualisation = CompletionVisualisation.MULTI_COLUMN
        self.completion_menu_scroll_offset = 1

        self.show_line_numbers = False
        self.show_status_bar = True
        self.wrap_lines = True
        self.complete_while_typing = True
        self.vi_mode = vi_mode
        self.paste_mode = False  # When True, don't insert whitespace after newline.
        self.confirm_exit = True  # Ask for confirmation when Control-D is pressed.
        self.accept_input_on_enter = 2  # Accept when pressing Enter 'n' times.
        # 'None' means that meta-enter is always required.
        self.enable_open_in_editor = True
        self.enable_system_bindings = True
        self.enable_input_validation = True
        self.enable_auto_suggest = False
        self.enable_mouse_support = False
        self.enable_history_search = False  # When True, like readline, going
        # back in history will filter the
        # history on the records starting
        # with the current input.

        self.highlight_matching_parenthesis = False
        self.show_sidebar = False  # Currently show the sidebar.
        self.show_sidebar_help = True  # When the sidebar is visible, also show the help text.
        self.show_exit_confirmation = False  # Currently show 'Do you really want to exit?'
        self.terminal_title = None  # The title to be displayed in the terminal. (None or string.)
        self.exit_message = 'Do you really want to exit?'
        self.insert_blank_line_after_output = True  # (For the REPL.)

        # Tokens to be shown at the prompt.
        self.prompt_style = 'classic'  # The currently active style.

        self.all_prompt_styles = {  # Styles selectable from the menu.
            'ipython': IPythonPrompt(self),
            'classic': ClassicPrompt(),
        }

        self.get_input_prompt_tokens = lambda cli: \
            self.all_prompt_styles[self.prompt_style].in_tokens(cli)

        self.get_output_prompt_tokens = lambda cli: \
            self.all_prompt_styles[self.prompt_style].out_tokens(cli)

        #: Load styles.
        self.code_styles = get_all_code_styles()
        self.ui_styles = get_all_ui_styles()
        self._current_code_style_name = 'default'
        self._current_ui_style_name = 'default'

        if is_windows():
            self._current_code_style_name = 'win32'

        self._current_style = self._generate_style()
        self.true_color = False

        # Options to be configurable from the sidebar.
        self.options = self._create_options()
        self.selected_option_index = 0

        #: Incremeting integer counting the current statement.
        self.current_statement_index = 1

        # Code signatures. (This is set asynchronously after a timeout.)
        self.signatures = []

        # Create a Registry for the key bindings.
        self.key_bindings_registry = MergedRegistry([
            ConditionalRegistry(
                registry=load_key_bindings_for_prompt(
                    enable_abort_and_exit_bindings=True,
                    enable_search=True,
                    enable_open_in_editor=Condition(
                        lambda cli: self.enable_open_in_editor),
                    enable_system_bindings=Condition(
                        lambda cli: self.enable_system_bindings),
                    enable_auto_suggest_bindings=Condition(
                        lambda cli: self.enable_auto_suggest)),

                # Disable all default key bindings when the sidebar or the exit confirmation
                # are shown.
                filter=Condition(lambda cli: not (self.show_sidebar or self.
                                                  show_exit_confirmation))),
            load_mouse_bindings(),
            load_python_bindings(self),
            load_sidebar_bindings(self),
            load_confirm_exit_bindings(self),
        ])

        # Boolean indicating whether we have a signatures thread running.
        # (Never run more than one at the same time.)
        self._get_signatures_thread_running = False
Beispiel #8
0
class PythonInput(object):
    """
    Prompt for reading Python input.

    ::

        python_input = PythonInput(...)
        application = python_input.create_application()
        cli = PythonCommandLineInterface(application=application)
        python_code = cli.run()
    """
    def __init__(
            self,
            get_globals=None,
            get_locals=None,
            history_filename=None,
            vi_mode=False,

            # For internal use.
            _completer=None,
            _validator=None,
            _lexer=None,
            _extra_buffers=None,
            _extra_buffer_processors=None,
            _on_start=None,
            _extra_layout_body=None,
            _extra_toolbars=None,
            _input_buffer_height=None,
            _accept_action=AcceptAction.RETURN_DOCUMENT,
            _on_exit=AbortAction.RAISE_EXCEPTION):

        self.get_globals = get_globals or (lambda: {})
        self.get_locals = get_locals or self.get_globals

        self._completer = _completer or PythonCompleter(
            self.get_globals, self.get_locals)
        self._validator = _validator or PythonValidator(
            self.get_compiler_flags)
        self.history = FileHistory(
            history_filename) if history_filename else InMemoryHistory()
        self._lexer = _lexer or PygmentsLexer(PythonLexer)
        self._extra_buffers = _extra_buffers
        self._accept_action = _accept_action
        self._on_exit = _on_exit
        self._on_start = _on_start

        self._input_buffer_height = _input_buffer_height
        self._extra_layout_body = _extra_layout_body or []
        self._extra_toolbars = _extra_toolbars or []
        self._extra_buffer_processors = _extra_buffer_processors or []

        # Settings.
        self.show_signature = False
        self.show_docstring = False
        self.show_meta_enter_message = True
        self.completion_visualisation = CompletionVisualisation.MULTI_COLUMN
        self.completion_menu_scroll_offset = 1

        self.show_line_numbers = False
        self.show_status_bar = True
        self.wrap_lines = True
        self.complete_while_typing = True
        self.vi_mode = vi_mode
        self.paste_mode = False  # When True, don't insert whitespace after newline.
        self.confirm_exit = True  # Ask for confirmation when Control-D is pressed.
        self.accept_input_on_enter = 2  # Accept when pressing Enter 'n' times.
        # 'None' means that meta-enter is always required.
        self.enable_open_in_editor = True
        self.enable_system_bindings = True
        self.enable_input_validation = True
        self.enable_auto_suggest = False
        self.enable_mouse_support = False
        self.enable_history_search = False  # When True, like readline, going
        # back in history will filter the
        # history on the records starting
        # with the current input.

        self.highlight_matching_parenthesis = False
        self.show_sidebar = False  # Currently show the sidebar.
        self.show_sidebar_help = True  # When the sidebar is visible, also show the help text.
        self.show_exit_confirmation = False  # Currently show 'Do you really want to exit?'
        self.terminal_title = None  # The title to be displayed in the terminal. (None or string.)
        self.exit_message = 'Do you really want to exit?'
        self.insert_blank_line_after_output = True  # (For the REPL.)

        # Tokens to be shown at the prompt.
        self.prompt_style = 'classic'  # The currently active style.

        self.all_prompt_styles = {  # Styles selectable from the menu.
            'ipython': IPythonPrompt(self),
            'classic': ClassicPrompt(),
        }

        self.get_input_prompt_tokens = lambda cli: \
            self.all_prompt_styles[self.prompt_style].in_tokens(cli)

        self.get_output_prompt_tokens = lambda cli: \
            self.all_prompt_styles[self.prompt_style].out_tokens(cli)

        #: Load styles.
        self.code_styles = get_all_code_styles()
        self.ui_styles = get_all_ui_styles()
        self._current_code_style_name = 'default'
        self._current_ui_style_name = 'default'

        if is_windows():
            self._current_code_style_name = 'win32'

        self._current_style = self._generate_style()
        self.true_color = False

        # Options to be configurable from the sidebar.
        self.options = self._create_options()
        self.selected_option_index = 0

        #: Incremeting integer counting the current statement.
        self.current_statement_index = 1

        # Code signatures. (This is set asynchronously after a timeout.)
        self.signatures = []

        # Create a Registry for the key bindings.
        self.key_bindings_registry = MergedRegistry([
            ConditionalRegistry(
                registry=load_key_bindings_for_prompt(
                    enable_abort_and_exit_bindings=True,
                    enable_search=True,
                    enable_open_in_editor=Condition(
                        lambda cli: self.enable_open_in_editor),
                    enable_system_bindings=Condition(
                        lambda cli: self.enable_system_bindings),
                    enable_auto_suggest_bindings=Condition(
                        lambda cli: self.enable_auto_suggest)),

                # Disable all default key bindings when the sidebar or the exit confirmation
                # are shown.
                filter=Condition(lambda cli: not (self.show_sidebar or self.
                                                  show_exit_confirmation))),
            load_mouse_bindings(),
            load_python_bindings(self),
            load_sidebar_bindings(self),
            load_confirm_exit_bindings(self),
        ])

        # Boolean indicating whether we have a signatures thread running.
        # (Never run more than one at the same time.)
        self._get_signatures_thread_running = False

    @property
    def option_count(self):
        " Return the total amount of options. (In all categories together.) "
        return sum(len(category.options) for category in self.options)

    @property
    def selected_option(self):
        " Return the currently selected option. "
        i = 0
        for category in self.options:
            for o in category.options:
                if i == self.selected_option_index:
                    return o
                else:
                    i += 1

    def get_compiler_flags(self):
        """
        Give the current compiler flags by looking for _Feature instances
        in the globals.
        """
        flags = 0

        for value in self.get_globals().values():
            if isinstance(value, __future__._Feature):
                flags |= value.compiler_flag

        return flags

    @property
    def add_key_binding(self):
        """
        Shortcut for adding new key bindings.
        (Mostly useful for a .ptpython/config.py file, that receives
        a PythonInput/Repl instance as input.)

        ::

            @python_input.add_key_binding(Keys.ControlX, filter=...)
            def handler(event):
                ...
        """
        # Extra key bindings should not be active when the sidebar is visible.
        sidebar_visible = Condition(lambda cli: self.show_sidebar)

        def add_binding_decorator(*keys, **kw):
            # Pop default filter keyword argument.
            filter = kw.pop('filter', Always())
            assert not kw

            return self.key_bindings_registry.add_binding(*keys,
                                                          filter=filter
                                                          & ~sidebar_visible)

        return add_binding_decorator

    def install_code_colorscheme(self, name, style_dict):
        """
        Install a new code color scheme.
        """
        assert isinstance(name, six.text_type)
        assert isinstance(style_dict, dict)

        self.code_styles[name] = style_dict

    def use_code_colorscheme(self, name):
        """
        Apply new colorscheme. (By name.)
        """
        assert name in self.code_styles

        self._current_code_style_name = name
        self._current_style = self._generate_style()

    def install_ui_colorscheme(self, name, style_dict):
        """
        Install a new UI color scheme.
        """
        assert isinstance(name, six.text_type)
        assert isinstance(style_dict, dict)

        self.ui_styles[name] = style_dict

    def use_ui_colorscheme(self, name):
        """
        Apply new colorscheme. (By name.)
        """
        assert name in self.ui_styles

        self._current_ui_style_name = name
        self._current_style = self._generate_style()

    def _generate_style(self):
        """
        Create new Style instance.
        (We don't want to do this on every key press, because each time the
        renderer receives a new style class, he will redraw everything.)
        """
        return generate_style(self.code_styles[self._current_code_style_name],
                              self.ui_styles[self._current_ui_style_name])

    def _create_options(self):
        """
        Create a list of `Option` instances for the options sidebar.
        """
        def enable(attribute, value=True):
            setattr(self, attribute, value)

            # Return `True`, to be able to chain this in the lambdas below.
            return True

        def disable(attribute):
            setattr(self, attribute, False)
            return True

        def simple_option(title, description, field_name, values=None):
            " Create Simple on/of option. "
            values = values or ['off', 'on']

            def get_current_value():
                return values[bool(getattr(self, field_name))]

            def get_values():
                return {
                    values[1]: lambda: enable(field_name),
                    values[0]: lambda: disable(field_name),
                }

            return Option(title=title,
                          description=description,
                          get_values=get_values,
                          get_current_value=get_current_value)

        return [
            OptionCategory('Input', [
                simple_option(title='Input mode',
                              description='Vi or emacs key bindings.',
                              field_name='vi_mode',
                              values=['emacs', 'vi']),
                simple_option(
                    title='Paste mode',
                    description="When enabled, don't indent automatically.",
                    field_name='paste_mode'),
                Option(
                    title='Complete while typing',
                    description=
                    "Generate autocompletions automatically while typing. "
                    'Don\'t require pressing TAB. (Not compatible with "History search".)',
                    get_current_value=lambda: ['off', 'on'][
                        self.complete_while_typing],
                    get_values=lambda: {
                        'on':
                        lambda: enable('complete_while_typing') and disable(
                            'enable_history_search'),
                        'off':
                        lambda: disable('complete_while_typing'),
                    }),
                Option(
                    title='History search',
                    description=
                    'When pressing the up-arrow, filter the history on input starting '
                    'with the current text. (Not compatible with "Complete while typing".)',
                    get_current_value=lambda: ['off', 'on'][
                        self.enable_history_search],
                    get_values=lambda: {
                        'on':
                        lambda: enable('enable_history_search') and disable(
                            'complete_while_typing'),
                        'off':
                        lambda: disable('enable_history_search'),
                    }),
                simple_option(
                    title='Mouse support',
                    description=
                    'Respond to mouse clicks and scrolling for positioning the cursor, '
                    'selecting text and scrolling through windows.',
                    field_name='enable_mouse_support'),
                simple_option(title='Confirm on exit',
                              description='Require confirmation when exiting.',
                              field_name='confirm_exit'),
                simple_option(
                    title='Input validation',
                    description=
                    'In case of syntax errors, move the cursor to the error '
                    'instead of showing a traceback of a SyntaxError.',
                    field_name='enable_input_validation'),
                simple_option(
                    title='Auto suggestion',
                    description='Auto suggest inputs by looking at the history. '
                    'Pressing right arrow or Ctrl-E will complete the entry.',
                    field_name='enable_auto_suggest'),
                Option(
                    title='Accept input on enter',
                    description=
                    'Amount of ENTER presses required to execute input when the cursor '
                    'is at the end of the input. (Note that META+ENTER will always execute.)',
                    get_current_value=lambda: str(self.accept_input_on_enter or
                                                  'meta-enter'),
                    get_values=lambda: {
                        '2':
                        lambda: enable('accept_input_on_enter', 2),
                        '3':
                        lambda: enable('accept_input_on_enter', 3),
                        '4':
                        lambda: enable('accept_input_on_enter', 4),
                        'meta-enter':
                        lambda: enable('accept_input_on_enter', None),
                    }),
            ]),
            OptionCategory('Display', [
                Option(
                    title='Completions',
                    description=
                    'Visualisation to use for displaying the completions. (Multiple columns, one column, a toolbar or nothing.)',
                    get_current_value=lambda: self.completion_visualisation,
                    get_values=lambda: {
                        CompletionVisualisation.NONE:
                        lambda: enable('completion_visualisation',
                                       CompletionVisualisation.NONE),
                        CompletionVisualisation.POP_UP:
                        lambda: enable('completion_visualisation',
                                       CompletionVisualisation.POP_UP),
                        CompletionVisualisation.MULTI_COLUMN:
                        lambda: enable('completion_visualisation',
                                       CompletionVisualisation.MULTI_COLUMN),
                        CompletionVisualisation.TOOLBAR:
                        lambda: enable('completion_visualisation',
                                       CompletionVisualisation.TOOLBAR),
                    }),
                Option(title='Prompt',
                       description=
                       "Visualisation of the prompt. ('>>>' or 'In [1]:')",
                       get_current_value=lambda: self.prompt_style,
                       get_values=lambda: dict(
                           (s, partial(enable, 'prompt_style', s))
                           for s in self.all_prompt_styles)),
                simple_option(
                    title='Blank line after output',
                    description='Insert a blank line after the output.',
                    field_name='insert_blank_line_after_output'),
                simple_option(title='Show signature',
                              description='Display function signatures.',
                              field_name='show_signature'),
                simple_option(title='Show docstring',
                              description='Display function docstrings.',
                              field_name='show_docstring'),
                simple_option(
                    title='Show line numbers',
                    description=
                    'Show line numbers when the input consists of multiple lines.',
                    field_name='show_line_numbers'),
                simple_option(
                    title='Show Meta+Enter message',
                    description=
                    'Show the [Meta+Enter] message when this key combination is required to execute commands. '
                    +
                    '(This is the case when a simple [Enter] key press will insert a newline.',
                    field_name='show_meta_enter_message'),
                simple_option(
                    title='Wrap lines',
                    description='Wrap lines instead of scrolling horizontally.',
                    field_name='wrap_lines'),
                simple_option(
                    title='Show status bar',
                    description=
                    'Show the status bar at the bottom of the terminal.',
                    field_name='show_status_bar'),
                simple_option(
                    title='Show sidebar help',
                    description=
                    'When the sidebar is visible, also show this help text.',
                    field_name='show_sidebar_help'),
                simple_option(
                    title='Highlight parenthesis',
                    description=
                    'Highlight matching parenthesis, when the cursor is on or right after one.',
                    field_name='highlight_matching_parenthesis'),
            ]),
            OptionCategory('Colors', [
                Option(title='Code',
                       description='Color scheme to use for the Python code.',
                       get_current_value=lambda: self._current_code_style_name,
                       get_values=lambda: dict(
                           (name, partial(self.use_code_colorscheme, name))
                           for name in self.code_styles)),
                Option(
                    title='User interface',
                    description='Color scheme to use for the user interface.',
                    get_current_value=lambda: self._current_ui_style_name,
                    get_values=lambda: dict(
                        (name, partial(self.use_ui_colorscheme, name))
                        for name in self.ui_styles)),
                simple_option(
                    title='True color (24 bit)',
                    description='Use 24 bit colors instead of 265 colors',
                    field_name='true_color'),
            ]),
        ]

    def create_application(self):
        """
        Create an `Application` instance for use in a `CommandLineInterface`.
        """
        buffers = {
            'docstring': Buffer(read_only=True),
        }
        buffers.update(self._extra_buffers or {})

        return Application(
            layout=create_layout(
                self,
                lexer=self._lexer,
                input_buffer_height=self._input_buffer_height,
                extra_buffer_processors=self._extra_buffer_processors,
                extra_body=self._extra_layout_body,
                extra_toolbars=self._extra_toolbars),
            buffer=self._create_buffer(),
            buffers=buffers,
            key_bindings_registry=self.key_bindings_registry,
            paste_mode=Condition(lambda cli: self.paste_mode),
            mouse_support=Condition(lambda cli: self.enable_mouse_support),
            on_abort=AbortAction.RETRY,
            on_exit=self._on_exit,
            style=DynamicStyle(lambda: self._current_style),
            get_title=lambda: self.terminal_title,
            on_initialize=self._on_cli_initialize,
            on_start=self._on_start,
            on_input_timeout=self._on_input_timeout)

    def _create_buffer(self):
        """
        Create the `Buffer` for the Python input.
        """
        def is_buffer_multiline():
            return (self.paste_mode or self.accept_input_on_enter is None
                    or document_is_multiline_python(python_buffer.document))

        python_buffer = Buffer(
            is_multiline=Condition(is_buffer_multiline),
            complete_while_typing=Condition(
                lambda: self.complete_while_typing),
            enable_history_search=Condition(
                lambda: self.enable_history_search),
            tempfile_suffix='.py',
            history=self.history,
            completer=self._completer,
            validator=ConditionalValidator(
                self._validator,
                Condition(lambda: self.enable_input_validation)),
            auto_suggest=ConditionalAutoSuggest(
                AutoSuggestFromHistory(),
                Condition(lambda cli: self.enable_auto_suggest)),
            accept_action=self._accept_action)

        return python_buffer

    def _on_cli_initialize(self, cli):
        """
        Called when a CommandLineInterface has been created.
        """

        # Synchronize PythonInput state with the CommandLineInterface.
        def synchronize(_=None):
            if self.vi_mode:
                cli.editing_mode = EditingMode.VI
            else:
                cli.editing_mode = EditingMode.EMACS

        cli.input_processor.beforeKeyPress += synchronize
        cli.input_processor.afterKeyPress += synchronize
        synchronize()

    def _on_input_timeout(self, cli):
        """
        When there is no input activity,
        in another thread, get the signature of the current code.
        """
        if cli.current_buffer_name != DEFAULT_BUFFER:
            return

        # Never run multiple get-signature threads.
        if self._get_signatures_thread_running:
            return
        self._get_signatures_thread_running = True

        buffer = cli.current_buffer
        document = buffer.document

        def run():
            script = get_jedi_script_from_document(document, self.get_locals(),
                                                   self.get_globals())

            # Show signatures in help text.
            if script:
                try:
                    signatures = script.call_signatures()
                except ValueError:
                    # e.g. in case of an invalid \\x escape.
                    signatures = []
                except Exception:
                    # Sometimes we still get an exception (TypeError), because
                    # of probably bugs in jedi. We can silence them.
                    # See: https://github.com/davidhalter/jedi/issues/492
                    signatures = []
                else:
                    # Try to access the params attribute just once. For Jedi
                    # signatures containing the keyword-only argument star,
                    # this will crash when retrieving it the first time with
                    # AttributeError. Every following time it works.
                    # See: https://github.com/jonathanslenders/ptpython/issues/47
                    #      https://github.com/davidhalter/jedi/issues/598
                    try:
                        if signatures:
                            signatures[0].params
                    except AttributeError:
                        pass
            else:
                signatures = []

            self._get_signatures_thread_running = False

            # Set signatures and redraw if the text didn't change in the
            # meantime. Otherwise request new signatures.
            if buffer.text == document.text:
                self.signatures = signatures

                # Set docstring in docstring buffer.
                if signatures:
                    string = signatures[0].docstring()
                    if not isinstance(string, six.text_type):
                        string = string.decode('utf-8')
                    cli.buffers['docstring'].reset(
                        initial_document=Document(string, cursor_position=0))
                else:
                    cli.buffers['docstring'].reset()

                cli.request_redraw()
            else:
                self._on_input_timeout(cli)

        cli.eventloop.run_in_executor(run)

    def on_reset(self, cli):
        self.signatures = []

    def enter_history(self, cli):
        """
        Display the history.
        """
        cli.vi_state.input_mode = InputMode.NAVIGATION

        def done(result):
            if result is not None:
                cli.buffers[DEFAULT_BUFFER].document = result

            cli.vi_state.input_mode = InputMode.INSERT

        cli.run_sub_application(
            create_history_application(self,
                                       cli.buffers[DEFAULT_BUFFER].document),
            done)
    def __init__(self,
                 get_globals=None, get_locals=None, history_filename=None,
                 vi_mode=False,

                 # For internal use.
                 _completer=None, _validator=None,
                 _lexer=None, _extra_buffers=None, _extra_buffer_processors=None,
                 _on_start=None,
                 _extra_layout_body=None, _extra_toolbars=None,
                 _input_buffer_height=None,
                 _accept_action=AcceptAction.RETURN_DOCUMENT,
                 _on_exit=AbortAction.RAISE_EXCEPTION):

        self.get_globals = get_globals or (lambda: {})
        self.get_locals = get_locals or self.get_globals

        self._completer = _completer or PythonCompleter(self.get_globals, self.get_locals)
        self._validator = _validator or PythonValidator(self.get_compiler_flags)
        self.history = FileHistory(history_filename) if history_filename else InMemoryHistory()
        self._lexer = _lexer or PygmentsLexer(PythonLexer)
        self._extra_buffers = _extra_buffers
        self._accept_action = _accept_action
        self._on_exit = _on_exit
        self._on_start = _on_start

        self._input_buffer_height = _input_buffer_height
        self._extra_layout_body = _extra_layout_body or []
        self._extra_toolbars = _extra_toolbars or []
        self._extra_buffer_processors = _extra_buffer_processors or []

        # Settings.
        self.show_signature = False
        self.show_docstring = False
        self.show_meta_enter_message = True
        self.completion_visualisation = CompletionVisualisation.MULTI_COLUMN
        self.completion_menu_scroll_offset = 1

        self.show_line_numbers = False
        self.show_status_bar = True
        self.wrap_lines = True
        self.complete_while_typing = True
        self.vi_mode = vi_mode
        self.paste_mode = False  # When True, don't insert whitespace after newline.
        self.confirm_exit = True  # Ask for confirmation when Control-D is pressed.
        self.accept_input_on_enter = 2  # Accept when pressing Enter 'n' times.
                                        # 'None' means that meta-enter is always required.
        self.enable_open_in_editor = True
        self.enable_system_bindings = True
        self.enable_input_validation = True
        self.enable_auto_suggest = False
        self.enable_mouse_support = False
        self.enable_history_search = False  # When True, like readline, going
                                            # back in history will filter the
                                            # history on the records starting
                                            # with the current input.

        self.highlight_matching_parenthesis = False
        self.show_sidebar = False  # Currently show the sidebar.
        self.show_sidebar_help = True # When the sidebar is visible, also show the help text.
        self.show_exit_confirmation = False  # Currently show 'Do you really want to exit?'
        self.terminal_title = None  # The title to be displayed in the terminal. (None or string.)
        self.exit_message = 'Do you really want to exit?'
        self.insert_blank_line_after_output = True  # (For the REPL.)

        # Tokens to be shown at the prompt.
        self.prompt_style = 'classic'  # The currently active style.

        self.all_prompt_styles = {  # Styles selectable from the menu.
            'ipython': IPythonPrompt(self),
            'classic': ClassicPrompt(),
        }

        self.get_input_prompt_tokens = lambda cli: \
            self.all_prompt_styles[self.prompt_style].in_tokens(cli)

        self.get_output_prompt_tokens = lambda cli: \
            self.all_prompt_styles[self.prompt_style].out_tokens(cli)

        #: Load styles.
        self.code_styles = get_all_code_styles()
        self.ui_styles = get_all_ui_styles()
        self._current_code_style_name = 'default'
        self._current_ui_style_name = 'default'

        if is_windows():
            self._current_code_style_name = 'win32'

        self._current_style = self._generate_style()
        self.true_color = False

        # Options to be configurable from the sidebar.
        self.options = self._create_options()
        self.selected_option_index = 0

        #: Incremeting integer counting the current statement.
        self.current_statement_index = 1

        # Code signatures. (This is set asynchronously after a timeout.)
        self.signatures = []

        # Create a Registry for the key bindings.
        self.key_bindings_registry = MergedRegistry([
            ConditionalRegistry(
                registry=load_key_bindings_for_prompt(
                    enable_abort_and_exit_bindings=True,
                    enable_search=True,
                    enable_open_in_editor=Condition(lambda cli: self.enable_open_in_editor),
                    enable_system_bindings=Condition(lambda cli: self.enable_system_bindings),
                    enable_auto_suggest_bindings=Condition(lambda cli: self.enable_auto_suggest)),

                # Disable all default key bindings when the sidebar or the exit confirmation
                # are shown.
                filter=Condition(lambda cli: not (self.show_sidebar or self.show_exit_confirmation))
            ),
            load_mouse_bindings(),
            load_python_bindings(self),
            load_sidebar_bindings(self),
            load_confirm_exit_bindings(self),
        ])

        # Boolean indicating whether we have a signatures thread running.
        # (Never run more than one at the same time.)
        self._get_signatures_thread_running = False
class PythonInput(object):
    """
    Prompt for reading Python input.

    ::

        python_input = PythonInput(...)
        application = python_input.create_application()
        cli = PythonCommandLineInterface(application=application)
        python_code = cli.run()
    """
    def __init__(self,
                 get_globals=None, get_locals=None, history_filename=None,
                 vi_mode=False,

                 # For internal use.
                 _completer=None, _validator=None,
                 _lexer=None, _extra_buffers=None, _extra_buffer_processors=None,
                 _on_start=None,
                 _extra_layout_body=None, _extra_toolbars=None,
                 _input_buffer_height=None,
                 _accept_action=AcceptAction.RETURN_DOCUMENT,
                 _on_exit=AbortAction.RAISE_EXCEPTION):

        self.get_globals = get_globals or (lambda: {})
        self.get_locals = get_locals or self.get_globals

        self._completer = _completer or PythonCompleter(self.get_globals, self.get_locals)
        self._validator = _validator or PythonValidator(self.get_compiler_flags)
        self.history = FileHistory(history_filename) if history_filename else InMemoryHistory()
        self._lexer = _lexer or PygmentsLexer(PythonLexer)
        self._extra_buffers = _extra_buffers
        self._accept_action = _accept_action
        self._on_exit = _on_exit
        self._on_start = _on_start

        self._input_buffer_height = _input_buffer_height
        self._extra_layout_body = _extra_layout_body or []
        self._extra_toolbars = _extra_toolbars or []
        self._extra_buffer_processors = _extra_buffer_processors or []

        # Settings.
        self.show_signature = False
        self.show_docstring = False
        self.show_meta_enter_message = True
        self.completion_visualisation = CompletionVisualisation.MULTI_COLUMN
        self.completion_menu_scroll_offset = 1

        self.show_line_numbers = False
        self.show_status_bar = True
        self.wrap_lines = True
        self.complete_while_typing = True
        self.vi_mode = vi_mode
        self.paste_mode = False  # When True, don't insert whitespace after newline.
        self.confirm_exit = True  # Ask for confirmation when Control-D is pressed.
        self.accept_input_on_enter = 2  # Accept when pressing Enter 'n' times.
                                        # 'None' means that meta-enter is always required.
        self.enable_open_in_editor = True
        self.enable_system_bindings = True
        self.enable_input_validation = True
        self.enable_auto_suggest = False
        self.enable_mouse_support = False
        self.enable_history_search = False  # When True, like readline, going
                                            # back in history will filter the
                                            # history on the records starting
                                            # with the current input.

        self.highlight_matching_parenthesis = False
        self.show_sidebar = False  # Currently show the sidebar.
        self.show_sidebar_help = True # When the sidebar is visible, also show the help text.
        self.show_exit_confirmation = False  # Currently show 'Do you really want to exit?'
        self.terminal_title = None  # The title to be displayed in the terminal. (None or string.)
        self.exit_message = 'Do you really want to exit?'
        self.insert_blank_line_after_output = True  # (For the REPL.)

        # Tokens to be shown at the prompt.
        self.prompt_style = 'classic'  # The currently active style.

        self.all_prompt_styles = {  # Styles selectable from the menu.
            'ipython': IPythonPrompt(self),
            'classic': ClassicPrompt(),
        }

        self.get_input_prompt_tokens = lambda cli: \
            self.all_prompt_styles[self.prompt_style].in_tokens(cli)

        self.get_output_prompt_tokens = lambda cli: \
            self.all_prompt_styles[self.prompt_style].out_tokens(cli)

        #: Load styles.
        self.code_styles = get_all_code_styles()
        self.ui_styles = get_all_ui_styles()
        self._current_code_style_name = 'default'
        self._current_ui_style_name = 'default'

        if is_windows():
            self._current_code_style_name = 'win32'

        self._current_style = self._generate_style()
        self.true_color = False

        # Options to be configurable from the sidebar.
        self.options = self._create_options()
        self.selected_option_index = 0

        #: Incremeting integer counting the current statement.
        self.current_statement_index = 1

        # Code signatures. (This is set asynchronously after a timeout.)
        self.signatures = []

        # Create a Registry for the key bindings.
        self.key_bindings_registry = MergedRegistry([
            ConditionalRegistry(
                registry=load_key_bindings_for_prompt(
                    enable_abort_and_exit_bindings=True,
                    enable_search=True,
                    enable_open_in_editor=Condition(lambda cli: self.enable_open_in_editor),
                    enable_system_bindings=Condition(lambda cli: self.enable_system_bindings),
                    enable_auto_suggest_bindings=Condition(lambda cli: self.enable_auto_suggest)),

                # Disable all default key bindings when the sidebar or the exit confirmation
                # are shown.
                filter=Condition(lambda cli: not (self.show_sidebar or self.show_exit_confirmation))
            ),
            load_mouse_bindings(),
            load_python_bindings(self),
            load_sidebar_bindings(self),
            load_confirm_exit_bindings(self),
        ])

        # Boolean indicating whether we have a signatures thread running.
        # (Never run more than one at the same time.)
        self._get_signatures_thread_running = False

    @property
    def option_count(self):
        " Return the total amount of options. (In all categories together.) "
        return sum(len(category.options) for category in self.options)

    @property
    def selected_option(self):
        " Return the currently selected option. "
        i = 0
        for category in self.options:
            for o in category.options:
                if i == self.selected_option_index:
                    return o
                else:
                    i += 1

    def get_compiler_flags(self):
        """
        Give the current compiler flags by looking for _Feature instances
        in the globals.
        """
        flags = 0

        for value in self.get_globals().values():
            if isinstance(value, __future__._Feature):
                flags |= value.compiler_flag

        return flags

    @property
    def add_key_binding(self):
        """
        Shortcut for adding new key bindings.
        (Mostly useful for a .ptpython/config.py file, that receives
        a PythonInput/Repl instance as input.)

        ::

            @python_input.add_key_binding(Keys.ControlX, filter=...)
            def handler(event):
                ...
        """
        # Extra key bindings should not be active when the sidebar is visible.
        sidebar_visible = Condition(lambda cli: self.show_sidebar)

        def add_binding_decorator(*keys, **kw):
            # Pop default filter keyword argument.
            filter = kw.pop('filter', Always())
            assert not kw

            return self.key_bindings_registry.add_binding(*keys, filter=filter & ~sidebar_visible)
        return add_binding_decorator

    def install_code_colorscheme(self, name, style_dict):
        """
        Install a new code color scheme.
        """
        assert isinstance(name, six.text_type)
        assert isinstance(style_dict, dict)

        self.code_styles[name] = style_dict

    def use_code_colorscheme(self, name):
        """
        Apply new colorscheme. (By name.)
        """
        assert name in self.code_styles

        self._current_code_style_name = name
        self._current_style = self._generate_style()

    def install_ui_colorscheme(self, name, style_dict):
        """
        Install a new UI color scheme.
        """
        assert isinstance(name, six.text_type)
        assert isinstance(style_dict, dict)

        self.ui_styles[name] = style_dict

    def use_ui_colorscheme(self, name):
        """
        Apply new colorscheme. (By name.)
        """
        assert name in self.ui_styles

        self._current_ui_style_name = name
        self._current_style = self._generate_style()

    def _generate_style(self):
        """
        Create new Style instance.
        (We don't want to do this on every key press, because each time the
        renderer receives a new style class, he will redraw everything.)
        """
        return generate_style(self.code_styles[self._current_code_style_name],
                              self.ui_styles[self._current_ui_style_name])

    def _create_options(self):
        """
        Create a list of `Option` instances for the options sidebar.
        """
        def enable(attribute, value=True):
            setattr(self, attribute, value)

            # Return `True`, to be able to chain this in the lambdas below.
            return True

        def disable(attribute):
            setattr(self, attribute, False)
            return True

        def simple_option(title, description, field_name, values=None):
            " Create Simple on/of option. "
            values = values or ['off', 'on']

            def get_current_value():
                return values[bool(getattr(self, field_name))]

            def get_values():
                return {
                    values[1]: lambda: enable(field_name),
                    values[0]: lambda: disable(field_name),
                }

            return Option(title=title, description=description,
                          get_values=get_values,
                          get_current_value=get_current_value)

        return [
            OptionCategory('Input', [
                simple_option(title='Input mode',
                              description='Vi or emacs key bindings.',
                              field_name='vi_mode',
                              values=['emacs', 'vi']),
                simple_option(title='Paste mode',
                              description="When enabled, don't indent automatically.",
                              field_name='paste_mode'),
                Option(title='Complete while typing',
                       description="Generate autocompletions automatically while typing. "
                                   'Don\'t require pressing TAB. (Not compatible with "History search".)',
                       get_current_value=lambda: ['off', 'on'][self.complete_while_typing],
                       get_values=lambda: {
                           'on': lambda: enable('complete_while_typing') and disable('enable_history_search'),
                           'off': lambda: disable('complete_while_typing'),
                       }),
                Option(title='History search',
                       description='When pressing the up-arrow, filter the history on input starting '
                                   'with the current text. (Not compatible with "Complete while typing".)',
                       get_current_value=lambda: ['off', 'on'][self.enable_history_search],
                       get_values=lambda: {
                           'on': lambda: enable('enable_history_search') and disable('complete_while_typing'),
                           'off': lambda: disable('enable_history_search'),
                       }),
                simple_option(title='Mouse support',
                              description='Respond to mouse clicks and scrolling for positioning the cursor, '
                                          'selecting text and scrolling through windows.',
                              field_name='enable_mouse_support'),
                simple_option(title='Confirm on exit',
                              description='Require confirmation when exiting.',
                              field_name='confirm_exit'),
                simple_option(title='Input validation',
                              description='In case of syntax errors, move the cursor to the error '
                                          'instead of showing a traceback of a SyntaxError.',
                              field_name='enable_input_validation'),
                simple_option(title='Auto suggestion',
                              description='Auto suggest inputs by looking at the history. '
                                          'Pressing right arrow or Ctrl-E will complete the entry.',
                              field_name='enable_auto_suggest'),
                Option(title='Accept input on enter',
                       description='Amount of ENTER presses required to execute input when the cursor '
                                   'is at the end of the input. (Note that META+ENTER will always execute.)',
                       get_current_value=lambda: str(self.accept_input_on_enter or 'meta-enter'),
                       get_values=lambda: {
                           '2': lambda: enable('accept_input_on_enter', 2),
                           '3': lambda: enable('accept_input_on_enter', 3),
                           '4': lambda: enable('accept_input_on_enter', 4),
                           'meta-enter': lambda: enable('accept_input_on_enter', None),
                       }),
            ]),
            OptionCategory('Display', [
                Option(title='Completions',
                       description='Visualisation to use for displaying the completions. (Multiple columns, one column, a toolbar or nothing.)',
                       get_current_value=lambda: self.completion_visualisation,
                       get_values=lambda: {
                           CompletionVisualisation.NONE: lambda: enable('completion_visualisation', CompletionVisualisation.NONE),
                           CompletionVisualisation.POP_UP: lambda: enable('completion_visualisation', CompletionVisualisation.POP_UP),
                           CompletionVisualisation.MULTI_COLUMN: lambda: enable('completion_visualisation', CompletionVisualisation.MULTI_COLUMN),
                           CompletionVisualisation.TOOLBAR: lambda: enable('completion_visualisation', CompletionVisualisation.TOOLBAR),
                       }),
                Option(title='Prompt',
                       description="Visualisation of the prompt. ('>>>' or 'In [1]:')",
                       get_current_value=lambda: self.prompt_style,
                       get_values=lambda: dict((s, partial(enable, 'prompt_style', s)) for s in self.all_prompt_styles)),
                simple_option(title='Blank line after output',
                              description='Insert a blank line after the output.',
                              field_name='insert_blank_line_after_output'),
                simple_option(title='Show signature',
                              description='Display function signatures.',
                              field_name='show_signature'),
                simple_option(title='Show docstring',
                              description='Display function docstrings.',
                              field_name='show_docstring'),
                simple_option(title='Show line numbers',
                              description='Show line numbers when the input consists of multiple lines.',
                              field_name='show_line_numbers'),
                simple_option(title='Show Meta+Enter message',
                              description='Show the [Meta+Enter] message when this key combination is required to execute commands. ' +
                                  '(This is the case when a simple [Enter] key press will insert a newline.',
                              field_name='show_meta_enter_message'),
                simple_option(title='Wrap lines',
                              description='Wrap lines instead of scrolling horizontally.',
                              field_name='wrap_lines'),
                simple_option(title='Show status bar',
                              description='Show the status bar at the bottom of the terminal.',
                              field_name='show_status_bar'),
                simple_option(title='Show sidebar help',
                              description='When the sidebar is visible, also show this help text.',
                              field_name='show_sidebar_help'),
                simple_option(title='Highlight parenthesis',
                              description='Highlight matching parenthesis, when the cursor is on or right after one.',
                              field_name='highlight_matching_parenthesis'),
            ]),
            OptionCategory('Colors', [
                Option(title='Code',
                       description='Color scheme to use for the Python code.',
                       get_current_value=lambda: self._current_code_style_name,
                       get_values=lambda: dict(
                            (name, partial(self.use_code_colorscheme, name)) for name in self.code_styles)
                       ),
                Option(title='User interface',
                       description='Color scheme to use for the user interface.',
                       get_current_value=lambda: self._current_ui_style_name,
                       get_values=lambda: dict(
                            (name, partial(self.use_ui_colorscheme, name)) for name in self.ui_styles)
                       ),
                simple_option(title='True color (24 bit)',
                              description='Use 24 bit colors instead of 265 colors',
                              field_name='true_color'),
            ]),
        ]

    def create_application(self):
        """
        Create an `Application` instance for use in a `CommandLineInterface`.
        """
        buffers = {
            'docstring': Buffer(read_only=True),
        }
        buffers.update(self._extra_buffers or {})

        return Application(
            layout=create_layout(
                self,
                lexer=self._lexer,
                input_buffer_height=self._input_buffer_height,
                extra_buffer_processors=self._extra_buffer_processors,
                extra_body=self._extra_layout_body,
                extra_toolbars=self._extra_toolbars),
            buffer=self._create_buffer(),
            buffers=buffers,
            key_bindings_registry=self.key_bindings_registry,
            paste_mode=Condition(lambda cli: self.paste_mode),
            mouse_support=Condition(lambda cli: self.enable_mouse_support),
            on_abort=AbortAction.RETRY,
            on_exit=self._on_exit,
            style=DynamicStyle(lambda: self._current_style),
            get_title=lambda: self.terminal_title,
            reverse_vi_search_direction=True,
            on_initialize=self._on_cli_initialize,
            on_start=self._on_start,
            on_input_timeout=self._on_input_timeout)

    def _create_buffer(self):
        """
        Create the `Buffer` for the Python input.
        """
        def is_buffer_multiline():
            return (self.paste_mode or
                    self.accept_input_on_enter is None or
                    document_is_multiline_python(python_buffer.document))

        python_buffer = Buffer(
            is_multiline=Condition(is_buffer_multiline),
            complete_while_typing=Condition(lambda: self.complete_while_typing),
            enable_history_search=Condition(lambda: self.enable_history_search),
            tempfile_suffix='.py',
            history=self.history,
            completer=self._completer,
            validator=ConditionalValidator(
                self._validator,
                Condition(lambda: self.enable_input_validation)),
            auto_suggest=ConditionalAutoSuggest(
                AutoSuggestFromHistory(),
                Condition(lambda cli: self.enable_auto_suggest)),
            accept_action=self._accept_action)

        return python_buffer

    def _on_cli_initialize(self, cli):
        """
        Called when a CommandLineInterface has been created.
        """
        # Synchronize PythonInput state with the CommandLineInterface.
        def synchronize(_=None):
            if self.vi_mode:
                cli.editing_mode = EditingMode.VI
            else:
                cli.editing_mode = EditingMode.EMACS

        cli.input_processor.beforeKeyPress += synchronize
        cli.input_processor.afterKeyPress += synchronize
        synchronize()

    def _on_input_timeout(self, cli):
        """
        When there is no input activity,
        in another thread, get the signature of the current code.
        """
        if cli.current_buffer_name != DEFAULT_BUFFER:
            return

        # Never run multiple get-signature threads.
        if self._get_signatures_thread_running:
            return
        self._get_signatures_thread_running = True

        buffer = cli.current_buffer
        document = buffer.document

        def run():
            script = get_jedi_script_from_document(document, self.get_locals(), self.get_globals())

            # Show signatures in help text.
            if script:
                try:
                    signatures = script.call_signatures()
                except ValueError:
                    # e.g. in case of an invalid \\x escape.
                    signatures = []
                except Exception:
                    # Sometimes we still get an exception (TypeError), because
                    # of probably bugs in jedi. We can silence them.
                    # See: https://github.com/davidhalter/jedi/issues/492
                    signatures = []
                else:
                    # Try to access the params attribute just once. For Jedi
                    # signatures containing the keyword-only argument star,
                    # this will crash when retrieving it the first time with
                    # AttributeError. Every following time it works.
                    # See: https://github.com/jonathanslenders/ptpython/issues/47
                    #      https://github.com/davidhalter/jedi/issues/598
                    try:
                        if signatures:
                            signatures[0].params
                    except AttributeError:
                        pass
            else:
                signatures = []

            self._get_signatures_thread_running = False

            # Set signatures and redraw if the text didn't change in the
            # meantime. Otherwise request new signatures.
            if buffer.text == document.text:
                self.signatures = signatures

                # Set docstring in docstring buffer.
                if signatures:
                    string = signatures[0].docstring()
                    if not isinstance(string, six.text_type):
                        string = string.decode('utf-8')
                    cli.buffers['docstring'].reset(
                        initial_document=Document(string, cursor_position=0))
                else:
                    cli.buffers['docstring'].reset()

                cli.request_redraw()
            else:
                self._on_input_timeout(cli)

        cli.eventloop.run_in_executor(run)

    def on_reset(self, cli):
        self.signatures = []

    def enter_history(self, cli):
        """
        Display the history.
        """
        cli.vi_state.input_mode = InputMode.NAVIGATION

        def done(result):
            if result is not None:
                cli.buffers[DEFAULT_BUFFER].document = result

            cli.vi_state.input_mode = InputMode.INSERT

        cli.run_sub_application(create_history_application(
            self, cli.buffers[DEFAULT_BUFFER].document), done)
Beispiel #11
0
class KeyBindingsManager(object):
    """
    Pymux key binding manager.
    """
    def __init__(self, pymux):
        self.pymux = pymux

        def get_search_state(cli):
            " Return the currently active SearchState. (The one for the focussed pane.) "
            return pymux.arrangement.get_active_pane(cli).search_state

        # Start from this KeyBindingManager from prompt_toolkit, to have basic
        # editing functionality for the command line. These key binding are
        # however only active when the following `enable_all` condition is met.
        self.registry = MergedRegistry([
            ConditionalRegistry(
                registry=load_key_bindings(
                    enable_auto_suggest_bindings=True,
                    enable_search=
                    False,  # We have our own search bindings, that support multiple panes.
                    enable_extra_page_navigation=True,
                    get_search_state=get_search_state),
                filter=(HasFocus(COMMAND) | HasFocus(PROMPT)
                        | InScrollBuffer(pymux)) & ~HasPrefix(pymux),
            ),
            load_mouse_bindings(),
            self._load_builtins(),
            _load_search_bindings(pymux),
        ])

        self._prefix = (Keys.ControlB, )
        self._prefix_binding = None

        # Load initial bindings.
        self._load_prefix_binding()

        # Custom user configured key bindings.
        # { (needs_prefix, key) -> (command, handler) }
        self.custom_bindings = {}

    def _load_prefix_binding(self):
        """
        Load the prefix key binding.
        """
        pymux = self.pymux
        registry = self.registry

        # Remove previous binding.
        if self._prefix_binding:
            self.registry.remove_binding(self._prefix_binding)

        # Create new Python binding.
        @registry.add_binding(
            *self._prefix,
            filter=~(HasPrefix(pymux) | HasFocus(COMMAND) | HasFocus(PROMPT)
                     | WaitsForConfirmation(pymux)))
        def enter_prefix_handler(event):
            " Enter prefix mode. "
            pymux.get_client_state(event.cli).has_prefix = True

        self._prefix_binding = enter_prefix_handler

    @property
    def prefix(self):
        " Get the prefix key. "
        return self._prefix

    @prefix.setter
    def prefix(self, keys):
        """
        Set a new prefix key.
        """
        assert isinstance(keys, tuple)

        self._prefix = keys
        self._load_prefix_binding()

    def _load_builtins(self):
        """
        Fill the Registry with the hard coded key bindings.
        """
        pymux = self.pymux
        registry = Registry()

        # Create filters.
        has_prefix = HasPrefix(pymux)
        waits_for_confirmation = WaitsForConfirmation(pymux)
        prompt_or_command_focus = HasFocus(COMMAND) | HasFocus(PROMPT)
        display_pane_numbers = Condition(
            lambda cli: pymux.display_pane_numbers)
        in_scroll_buffer_not_searching = InScrollBufferNotSearching(pymux)
        pane_input_allowed = ~(prompt_or_command_focus | has_prefix
                               | waits_for_confirmation | display_pane_numbers
                               | InScrollBuffer(pymux))

        @registry.add_binding(Keys.Any,
                              filter=pane_input_allowed,
                              invalidate_ui=False)
        def _(event):
            """
            When a pane has the focus, key bindings are redirected to the
            process running inside the pane.
            """
            # NOTE: we don't invalidate the UI, because for pymux itself,
            #       nothing in the output changes yet. It's the application in
            #       the pane that will probably echo back the typed characters.
            #       When we receive them, they are draw to the UI and it's
            #       invalidated.
            w = pymux.arrangement.get_active_window(event.cli)
            pane = w.active_pane

            if pane.clock_mode:
                # Leave clock mode on key press.
                pane.clock_mode = False
                pymux.invalidate()
            else:
                # Write input to pane. If 'synchronize_panes' is on, write
                # input to all panes in the current window.
                panes = w.panes if w.synchronize_panes else [pane]
                for p in panes:
                    p.process.write_key(event.key_sequence[0].key)

        @registry.add_binding(Keys.BracketedPaste,
                              filter=pane_input_allowed,
                              invalidate_ui=False)
        def _(event):
            """
            Pasting to the active pane. (Using bracketed paste.)
            """
            w = pymux.arrangement.get_active_window(event.cli)
            pane = w.active_pane

            if not pane.clock_mode:
                # Paste input to pane. If 'synchronize_panes' is on, paste
                # input to all panes in the current window.
                panes = w.panes if w.synchronize_panes else [pane]
                for p in panes:
                    p.process.write_input(event.data, paste=True)

        @registry.add_binding(Keys.Any, filter=has_prefix)
        def _(event):
            " Ignore unknown Ctrl-B prefixed key sequences. "
            pymux.get_client_state(event.cli).has_prefix = False

        @registry.add_binding(Keys.ControlC,
                              filter=prompt_or_command_focus & ~has_prefix)
        @registry.add_binding(Keys.ControlG,
                              filter=prompt_or_command_focus & ~has_prefix)
        @registry.add_binding(
            Keys.Backspace,
            filter=HasFocus(COMMAND) & ~has_prefix
            & Condition(lambda cli: cli.buffers[COMMAND].text == ''))
        def _(event):
            " Leave command mode. "
            pymux.leave_command_mode(event.cli, append_to_history=False)

        @registry.add_binding('y', filter=waits_for_confirmation)
        @registry.add_binding('Y', filter=waits_for_confirmation)
        def _(event):
            """
            Confirm command.
            """
            client_state = pymux.get_client_state(event.cli)

            command = client_state.confirm_command
            client_state.confirm_command = None
            client_state.confirm_text = None

            pymux.handle_command(event.cli, command)

        @registry.add_binding('n', filter=waits_for_confirmation)
        @registry.add_binding('N', filter=waits_for_confirmation)
        @registry.add_binding(Keys.ControlC, filter=waits_for_confirmation)
        def _(event):
            """
            Cancel command.
            """
            client_state = pymux.get_client_state(event.cli)
            client_state.confirm_command = None
            client_state.confirm_text = None

        @registry.add_binding(Keys.ControlC,
                              filter=in_scroll_buffer_not_searching)
        @registry.add_binding(Keys.ControlJ,
                              filter=in_scroll_buffer_not_searching)
        @registry.add_binding('q', filter=in_scroll_buffer_not_searching)
        def _(event):
            " Exit scroll buffer. "
            pane = pymux.arrangement.get_active_pane(event.cli)
            pane.exit_scroll_buffer()

        @registry.add_binding(' ', filter=in_scroll_buffer_not_searching)
        def _(event):
            " Enter selection mode when pressing space in copy mode. "
            event.current_buffer.start_selection(
                selection_type=SelectionType.CHARACTERS)

        @registry.add_binding(Keys.ControlJ,
                              filter=in_scroll_buffer_not_searching
                              & HasSelection())
        def _(event):
            " Copy selection when pressing Enter. "
            clipboard_data = event.current_buffer.copy_selection()
            event.cli.clipboard.set_data(clipboard_data)

        @registry.add_binding('v',
                              filter=in_scroll_buffer_not_searching
                              & HasSelection())
        def _(event):
            " Toggle between selection types. "
            types = [
                SelectionType.LINES, SelectionType.BLOCK,
                SelectionType.CHARACTERS
            ]
            selection_state = event.current_buffer.selection_state

            try:
                index = types.index(selection_state.type)
            except ValueError:  # Not in list.
                index = 0

            selection_state.type = types[(index + 1) % len(types)]

        @registry.add_binding(Keys.Any, filter=display_pane_numbers)
        def _(event):
            " When the pane numbers are shown. Any key press should hide them. "
            pymux.display_pane_numbers = False

        return registry

    def add_custom_binding(self,
                           key_name,
                           command,
                           arguments,
                           needs_prefix=False):
        """
        Add custom binding (for the "bind-key" command.)
        Raises ValueError if the give `key_name` is an invalid name.

        :param key_name: Pymux key name, for instance "C-a" or "M-x".
        """
        assert isinstance(key_name, six.text_type)
        assert isinstance(command, six.text_type)
        assert isinstance(arguments, list)

        # Unbind previous key.
        self.remove_custom_binding(key_name, needs_prefix=needs_prefix)

        # Translate the pymux key name into a prompt_toolkit key sequence.
        # (Can raise ValueError.)
        keys_sequence = pymux_key_to_prompt_toolkit_key_sequence(key_name)

        # Create handler and add to Registry.
        if needs_prefix:
            filter = HasPrefix(self.pymux)
        else:
            filter = ~HasPrefix(self.pymux)

        filter = filter & ~(WaitsForConfirmation(self.pymux)
                            | HasFocus(COMMAND) | HasFocus(PROMPT))

        def key_handler(event):
            " The actual key handler. "
            call_command_handler(command, self.pymux, event.cli, arguments)
            self.pymux.get_client_state(event.cli).has_prefix = False

        self.registry.add_binding(*keys_sequence, filter=filter)(key_handler)

        # Store key in `custom_bindings` in order to be able to call
        # "unbind-key" later on.
        k = (needs_prefix, key_name)
        self.custom_bindings[k] = CustomBinding(key_handler, command,
                                                arguments)

    def remove_custom_binding(self, key_name, needs_prefix=False):
        """
        Remove custom key binding for a key.

        :param key_name: Pymux key name, for instance "C-A".
        """
        k = (needs_prefix, key_name)

        if k in self.custom_bindings:
            self.registry.remove_binding(self.custom_bindings[k].handler)
            del self.custom_bindings[k]
Beispiel #12
0
class KeyBindingsManager(object):
    """
    Pymux key binding manager.
    """
    def __init__(self, pymux):
        self.pymux = pymux

        def get_search_state(cli):
            " Return the currently active SearchState. (The one for the focussed pane.) "
            return pymux.arrangement.get_active_pane(cli).search_state

        # Start from this KeyBindingManager from prompt_toolkit, to have basic
        # editing functionality for the command line. These key binding are
        # however only active when the following `enable_all` condition is met.
        self.registry = MergedRegistry([
            ConditionalRegistry(
                registry=load_key_bindings(
                    enable_auto_suggest_bindings=True,
                    enable_search=False,  # We have our own search bindings, that support multiple panes.
                    enable_extra_page_navigation=True,
                    get_search_state=get_search_state),
                filter=(HasFocus(COMMAND) | HasFocus(PROMPT) |
                        InScrollBuffer(pymux)) & ~HasPrefix(pymux),
            ),
            load_mouse_bindings(),
            self._load_builtins(),
            _load_search_bindings(pymux),
        ])

        self._prefix = (Keys.ControlB, )
        self._prefix_binding = None

        # Load initial bindings.
        self._load_prefix_binding()

        # Custom user configured key bindings.
        # { (needs_prefix, key) -> (command, handler) }
        self.custom_bindings = {}

    def _load_prefix_binding(self):
        """
        Load the prefix key binding.
        """
        pymux = self.pymux
        registry = self.registry

        # Remove previous binding.
        if self._prefix_binding:
            self.registry.remove_binding(self._prefix_binding)

        # Create new Python binding.
        @registry.add_binding(*self._prefix, filter=
            ~(HasPrefix(pymux) | HasFocus(COMMAND) | HasFocus(PROMPT) | WaitsForConfirmation(pymux)))
        def enter_prefix_handler(event):
            " Enter prefix mode. "
            pymux.get_client_state(event.cli).has_prefix = True

        self._prefix_binding = enter_prefix_handler

    @property
    def prefix(self):
        " Get the prefix key. "
        return self._prefix

    @prefix.setter
    def prefix(self, keys):
        """
        Set a new prefix key.
        """
        assert isinstance(keys, tuple)

        self._prefix = keys
        self._load_prefix_binding()

    def _load_builtins(self):
        """
        Fill the Registry with the hard coded key bindings.
        """
        pymux = self.pymux
        registry = Registry()

        # Create filters.
        has_prefix = HasPrefix(pymux)
        waits_for_confirmation = WaitsForConfirmation(pymux)
        prompt_or_command_focus = HasFocus(COMMAND) | HasFocus(PROMPT)
        display_pane_numbers = Condition(lambda cli: pymux.display_pane_numbers)
        in_scroll_buffer_not_searching = InScrollBufferNotSearching(pymux)
        pane_input_allowed = ~(prompt_or_command_focus | has_prefix |
                               waits_for_confirmation | display_pane_numbers |
                               InScrollBuffer(pymux))

        @registry.add_binding(Keys.Any, filter=pane_input_allowed, invalidate_ui=False)
        def _(event):
            """
            When a pane has the focus, key bindings are redirected to the
            process running inside the pane.
            """
            # NOTE: we don't invalidate the UI, because for pymux itself,
            #       nothing in the output changes yet. It's the application in
            #       the pane that will probably echo back the typed characters.
            #       When we receive them, they are draw to the UI and it's
            #       invalidated.
            w = pymux.arrangement.get_active_window(event.cli)
            pane = w.active_pane

            if pane.clock_mode:
                # Leave clock mode on key press.
                pane.clock_mode = False
                pymux.invalidate()
            else:
                # Write input to pane. If 'synchronize_panes' is on, write
                # input to all panes in the current window.
                panes = w.panes if w.synchronize_panes else [pane]
                for p in panes:
                    p.process.write_key(event.key_sequence[0].key)

        @registry.add_binding(Keys.BracketedPaste, filter=pane_input_allowed, invalidate_ui=False)
        def _(event):
            """
            Pasting to the active pane. (Using bracketed paste.)
            """
            w = pymux.arrangement.get_active_window(event.cli)
            pane = w.active_pane

            if not pane.clock_mode:
                # Paste input to pane. If 'synchronize_panes' is on, paste
                # input to all panes in the current window.
                panes = w.panes if w.synchronize_panes else [pane]
                for p in panes:
                    p.process.write_input(event.data, paste=True)

        @registry.add_binding(Keys.Any, filter=has_prefix)
        def _(event):
            " Ignore unknown Ctrl-B prefixed key sequences. "
            pymux.get_client_state(event.cli).has_prefix = False

        @registry.add_binding(Keys.ControlC, filter=prompt_or_command_focus & ~has_prefix)
        @registry.add_binding(Keys.ControlG, filter=prompt_or_command_focus & ~has_prefix)
        @registry.add_binding(Keys.Backspace, filter=HasFocus(COMMAND) & ~has_prefix &
                              Condition(lambda cli: cli.buffers[COMMAND].text == ''))
        def _(event):
            " Leave command mode. "
            pymux.leave_command_mode(event.cli, append_to_history=False)

        @registry.add_binding('y', filter=waits_for_confirmation)
        @registry.add_binding('Y', filter=waits_for_confirmation)
        def _(event):
            """
            Confirm command.
            """
            client_state = pymux.get_client_state(event.cli)

            command = client_state.confirm_command
            client_state.confirm_command = None
            client_state.confirm_text = None

            pymux.handle_command(event.cli, command)

        @registry.add_binding('n', filter=waits_for_confirmation)
        @registry.add_binding('N', filter=waits_for_confirmation)
        @registry.add_binding(Keys.ControlC, filter=waits_for_confirmation)
        def _(event):
            """
            Cancel command.
            """
            client_state = pymux.get_client_state(event.cli)
            client_state.confirm_command = None
            client_state.confirm_text = None

        @registry.add_binding(Keys.ControlC, filter=in_scroll_buffer_not_searching)
        @registry.add_binding(Keys.ControlJ, filter=in_scroll_buffer_not_searching)
        @registry.add_binding('q', filter=in_scroll_buffer_not_searching)
        def _(event):
            " Exit scroll buffer. "
            pane = pymux.arrangement.get_active_pane(event.cli)
            pane.exit_scroll_buffer()

        @registry.add_binding(' ', filter=in_scroll_buffer_not_searching)
        def _(event):
            " Enter selection mode when pressing space in copy mode. "
            event.current_buffer.start_selection(selection_type=SelectionType.CHARACTERS)

        @registry.add_binding(Keys.ControlJ, filter=in_scroll_buffer_not_searching & HasSelection())
        def _(event):
            " Copy selection when pressing Enter. "
            clipboard_data = event.current_buffer.copy_selection()
            event.cli.clipboard.set_data(clipboard_data)

        @registry.add_binding('v', filter=in_scroll_buffer_not_searching & HasSelection())
        def _(event):
            " Toggle between selection types. "
            types = [SelectionType.LINES, SelectionType.BLOCK, SelectionType.CHARACTERS]
            selection_state = event.current_buffer.selection_state

            try:
                index = types.index(selection_state.type)
            except ValueError:  # Not in list.
                index = 0

            selection_state.type = types[(index + 1) % len(types)]

        @registry.add_binding(Keys.Any, filter=display_pane_numbers)
        def _(event):
            " When the pane numbers are shown. Any key press should hide them. "
            pymux.display_pane_numbers = False

        return registry

    def add_custom_binding(self, key_name, command, arguments, needs_prefix=False):
        """
        Add custom binding (for the "bind-key" command.)
        Raises ValueError if the give `key_name` is an invalid name.

        :param key_name: Pymux key name, for instance "C-a" or "M-x".
        """
        assert isinstance(key_name, six.text_type)
        assert isinstance(command, six.text_type)
        assert isinstance(arguments, list)

        # Unbind previous key.
        self.remove_custom_binding(key_name, needs_prefix=needs_prefix)

        # Translate the pymux key name into a prompt_toolkit key sequence.
        # (Can raise ValueError.)
        keys_sequence = pymux_key_to_prompt_toolkit_key_sequence(key_name)

        # Create handler and add to Registry.
        if needs_prefix:
            filter = HasPrefix(self.pymux)
        else:
            filter = ~HasPrefix(self.pymux)

        filter = filter & ~(WaitsForConfirmation(self.pymux) |
                            HasFocus(COMMAND) | HasFocus(PROMPT))

        def key_handler(event):
            " The actual key handler. "
            call_command_handler(command, self.pymux, event.cli, arguments)
            self.pymux.get_client_state(event.cli).has_prefix = False

        self.registry.add_binding(*keys_sequence, filter=filter)(key_handler)

        # Store key in `custom_bindings` in order to be able to call
        # "unbind-key" later on.
        k = (needs_prefix, key_name)
        self.custom_bindings[k] = CustomBinding(key_handler, command, arguments)

    def remove_custom_binding(self, key_name, needs_prefix=False):
        """
        Remove custom key binding for a key.

        :param key_name: Pymux key name, for instance "C-A".
        """
        k = (needs_prefix, key_name)

        if k in self.custom_bindings:
            self.registry.remove_binding(self.custom_bindings[k].handler)
            del self.custom_bindings[k]