1"""
2Key binding handlers for displaying completions.
3"""
4from __future__ import unicode_literals
5from prompt_toolkit.completion import CompleteEvent, get_common_complete_suffix
6from prompt_toolkit.utils import get_cwidth
7from prompt_toolkit.keys import Keys
8from prompt_toolkit.key_binding.registry import Registry
9
10import math
11
12__all__ = (
13    'generate_completions',
14    'display_completions_like_readline',
15)
16
17def generate_completions(event):
18    r"""
19    Tab-completion: where the first tab completes the common suffix and the
20    second tab lists all the completions.
21    """
22    b = event.current_buffer
23
24    # When already navigating through completions, select the next one.
25    if b.complete_state:
26        b.complete_next()
27    else:
28        event.cli.start_completion(insert_common_part=True, select_first=False)
29
30
31def display_completions_like_readline(event):
32    """
33    Key binding handler for readline-style tab completion.
34    This is meant to be as similar as possible to the way how readline displays
35    completions.
36
37    Generate the completions immediately (blocking) and display them above the
38    prompt in columns.
39
40    Usage::
41
42        # Call this handler when 'Tab' has been pressed.
43        registry.add_binding(Keys.ControlI)(display_completions_like_readline)
44    """
45    # Request completions.
46    b = event.current_buffer
47    if b.completer is None:
48        return
49    complete_event = CompleteEvent(completion_requested=True)
50    completions = list(b.completer.get_completions(b.document, complete_event))
51
52    # Calculate the common suffix.
53    common_suffix = get_common_complete_suffix(b.document, completions)
54
55    # One completion: insert it.
56    if len(completions) == 1:
57        b.delete_before_cursor(-completions[0].start_position)
58        b.insert_text(completions[0].text)
59    # Multiple completions with common part.
60    elif common_suffix:
61        b.insert_text(common_suffix)
62    # Otherwise: display all completions.
63    elif completions:
64        _display_completions_like_readline(event.cli, completions)
65
66
67def _display_completions_like_readline(cli, completions):
68    """
69    Display the list of completions in columns above the prompt.
70    This will ask for a confirmation if there are too many completions to fit
71    on a single page and provide a paginator to walk through them.
72    """
73    from prompt_toolkit.shortcuts import create_confirm_application
74    assert isinstance(completions, list)
75
76    # Get terminal dimensions.
77    term_size = cli.output.get_size()
78    term_width = term_size.columns
79    term_height = term_size.rows
80
81    # Calculate amount of required columns/rows for displaying the
82    # completions. (Keep in mind that completions are displayed
83    # alphabetically column-wise.)
84    max_compl_width = min(term_width,
85        max(get_cwidth(c.text) for c in completions) + 1)
86    column_count = max(1, term_width // max_compl_width)
87    completions_per_page = column_count * (term_height - 1)
88    page_count = int(math.ceil(len(completions) / float(completions_per_page)))
89        # Note: math.ceil can return float on Python2.
90
91    def display(page):
92        # Display completions.
93        page_completions = completions[page * completions_per_page:
94                                       (page+1) * completions_per_page]
95
96        page_row_count = int(math.ceil(len(page_completions) / float(column_count)))
97        page_columns = [page_completions[i * page_row_count:(i+1) * page_row_count]
98                   for i in range(column_count)]
99
100        result = []
101        for r in range(page_row_count):
102            for c in range(column_count):
103                try:
104                    result.append(page_columns[c][r].text.ljust(max_compl_width))
105                except IndexError:
106                    pass
107            result.append('\n')
108        cli.output.write(''.join(result))
109        cli.output.flush()
110
111    # User interaction through an application generator function.
112    def run():
113        if len(completions) > completions_per_page:
114            # Ask confirmation if it doesn't fit on the screen.
115            message = 'Display all {} possibilities? (y on n) '.format(len(completions))
116            confirm = yield create_confirm_application(message)
117
118            if confirm:
119                # Display pages.
120                for page in range(page_count):
121                    display(page)
122
123                    if page != page_count - 1:
124                        # Display --MORE-- and go to the next page.
125                        show_more = yield _create_more_application()
126                        if not show_more:
127                            return
128            else:
129                cli.output.write('\n'); cli.output.flush()
130        else:
131            # Display all completions.
132            display(0)
133
134    cli.run_application_generator(run, render_cli_done=True)
135
136
137def _create_more_application():
138    """
139    Create an `Application` instance that displays the "--MORE--".
140    """
141    from prompt_toolkit.shortcuts import create_prompt_application
142    registry = Registry()
143
144    @registry.add_binding(' ')
145    @registry.add_binding('y')
146    @registry.add_binding('Y')
147    @registry.add_binding(Keys.ControlJ)
148    @registry.add_binding(Keys.ControlI)  # Tab.
149    def _(event):
150        event.cli.set_return_value(True)
151
152    @registry.add_binding('n')
153    @registry.add_binding('N')
154    @registry.add_binding('q')
155    @registry.add_binding('Q')
156    @registry.add_binding(Keys.ControlC)
157    def _(event):
158        event.cli.set_return_value(False)
159
160    return create_prompt_application(
161        '--MORE--', key_bindings_registry=registry, erase_when_done=True)
162