Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/prompt_toolkit/key_binding/bindings/completion.py: 19%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

98 statements  

1""" 

2Key binding handlers for displaying completions. 

3""" 

4 

5from __future__ import annotations 

6 

7import asyncio 

8import math 

9from typing import TYPE_CHECKING 

10 

11from prompt_toolkit.application.run_in_terminal import in_terminal 

12from prompt_toolkit.completion import ( 

13 CompleteEvent, 

14 Completion, 

15 get_common_complete_suffix, 

16) 

17from prompt_toolkit.formatted_text import StyleAndTextTuples 

18from prompt_toolkit.key_binding.key_bindings import KeyBindings 

19from prompt_toolkit.key_binding.key_processor import KeyPressEvent 

20from prompt_toolkit.keys import Keys 

21from prompt_toolkit.utils import get_cwidth 

22 

23if TYPE_CHECKING: 

24 from prompt_toolkit.application import Application 

25 from prompt_toolkit.shortcuts import PromptSession 

26 

27__all__ = [ 

28 "generate_completions", 

29 "display_completions_like_readline", 

30] 

31 

32E = KeyPressEvent 

33 

34 

35def generate_completions(event: E) -> None: 

36 r""" 

37 Tab-completion: where the first tab completes the common suffix and the 

38 second tab lists all the completions. 

39 """ 

40 b = event.current_buffer 

41 

42 # When already navigating through completions, select the next one. 

43 if b.complete_state: 

44 b.complete_next() 

45 else: 

46 b.start_completion(insert_common_part=True) 

47 

48 

49def display_completions_like_readline(event: E) -> None: 

50 """ 

51 Key binding handler for readline-style tab completion. 

52 This is meant to be as similar as possible to the way how readline displays 

53 completions. 

54 

55 Generate the completions immediately (blocking) and display them above the 

56 prompt in columns. 

57 

58 Usage:: 

59 

60 # Call this handler when 'Tab' has been pressed. 

61 key_bindings.add(Keys.ControlI)(display_completions_like_readline) 

62 """ 

63 # Request completions. 

64 b = event.current_buffer 

65 if b.completer is None: 

66 return 

67 complete_event = CompleteEvent(completion_requested=True) 

68 completions = list(b.completer.get_completions(b.document, complete_event)) 

69 

70 # Calculate the common suffix. 

71 common_suffix = get_common_complete_suffix(b.document, completions) 

72 

73 # One completion: insert it. 

74 if len(completions) == 1: 

75 b.delete_before_cursor(-completions[0].start_position) 

76 b.insert_text(completions[0].text) 

77 # Multiple completions with common part. 

78 elif common_suffix: 

79 b.insert_text(common_suffix) 

80 # Otherwise: display all completions. 

81 elif completions: 

82 _display_completions_like_readline(event.app, completions) 

83 

84 

85def _display_completions_like_readline( 

86 app: Application[object], completions: list[Completion] 

87) -> asyncio.Task[None]: 

88 """ 

89 Display the list of completions in columns above the prompt. 

90 This will ask for a confirmation if there are too many completions to fit 

91 on a single page and provide a paginator to walk through them. 

92 """ 

93 from prompt_toolkit.formatted_text import to_formatted_text 

94 from prompt_toolkit.shortcuts.prompt import create_confirm_session 

95 

96 # Get terminal dimensions. 

97 term_size = app.output.get_size() 

98 term_width = term_size.columns 

99 term_height = term_size.rows 

100 

101 # Calculate amount of required columns/rows for displaying the 

102 # completions. (Keep in mind that completions are displayed 

103 # alphabetically column-wise.) 

104 max_compl_width = min( 

105 term_width, max(get_cwidth(c.display_text) for c in completions) + 1 

106 ) 

107 column_count = max(1, term_width // max_compl_width) 

108 completions_per_page = column_count * (term_height - 1) 

109 page_count = int(math.ceil(len(completions) / float(completions_per_page))) 

110 # Note: math.ceil can return float on Python2. 

111 

112 def display(page: int) -> None: 

113 # Display completions. 

114 page_completions = completions[ 

115 page * completions_per_page : (page + 1) * completions_per_page 

116 ] 

117 

118 page_row_count = int(math.ceil(len(page_completions) / float(column_count))) 

119 page_columns = [ 

120 page_completions[i * page_row_count : (i + 1) * page_row_count] 

121 for i in range(column_count) 

122 ] 

123 

124 result: StyleAndTextTuples = [] 

125 

126 for r in range(page_row_count): 

127 for c in range(column_count): 

128 try: 

129 completion = page_columns[c][r] 

130 style = "class:readline-like-completions.completion " + ( 

131 completion.style or "" 

132 ) 

133 

134 result.extend(to_formatted_text(completion.display, style=style)) 

135 

136 # Add padding. 

137 padding = max_compl_width - get_cwidth(completion.display_text) 

138 result.append((completion.style, " " * padding)) 

139 except IndexError: 

140 pass 

141 result.append(("", "\n")) 

142 

143 app.print_text(to_formatted_text(result, "class:readline-like-completions")) 

144 

145 # User interaction through an application generator function. 

146 async def run_compl() -> None: 

147 "Coroutine." 

148 async with in_terminal(render_cli_done=True): 

149 if len(completions) > completions_per_page: 

150 # Ask confirmation if it doesn't fit on the screen. 

151 confirm = await create_confirm_session( 

152 f"Display all {len(completions)} possibilities?", 

153 ).prompt_async() 

154 

155 if confirm: 

156 # Display pages. 

157 for page in range(page_count): 

158 display(page) 

159 

160 if page != page_count - 1: 

161 # Display --MORE-- and go to the next page. 

162 show_more = await _create_more_session( 

163 "--MORE--" 

164 ).prompt_async() 

165 

166 if not show_more: 

167 return 

168 else: 

169 app.output.flush() 

170 else: 

171 # Display all completions. 

172 display(0) 

173 

174 return app.create_background_task(run_compl()) 

175 

176 

177def _create_more_session(message: str = "--MORE--") -> PromptSession[bool]: 

178 """ 

179 Create a `PromptSession` object for displaying the "--MORE--". 

180 """ 

181 from prompt_toolkit.shortcuts import PromptSession 

182 

183 bindings = KeyBindings() 

184 

185 @bindings.add(" ") 

186 @bindings.add("y") 

187 @bindings.add("Y") 

188 @bindings.add(Keys.ControlJ) 

189 @bindings.add(Keys.ControlM) 

190 @bindings.add(Keys.ControlI) # Tab. 

191 def _yes(event: E) -> None: 

192 event.app.exit(result=True) 

193 

194 @bindings.add("n") 

195 @bindings.add("N") 

196 @bindings.add("q") 

197 @bindings.add("Q") 

198 @bindings.add(Keys.ControlC) 

199 def _no(event: E) -> None: 

200 event.app.exit(result=False) 

201 

202 @bindings.add(Keys.Any) 

203 def _ignore(event: E) -> None: 

204 "Disable inserting of text." 

205 

206 return PromptSession(message, key_bindings=bindings, erase_when_done=True)