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