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

97 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

1""" 

2Key binding handlers for displaying completions. 

3""" 

4from __future__ import annotations 

5 

6import asyncio 

7import math 

8from typing import TYPE_CHECKING 

9 

10from prompt_toolkit.application.run_in_terminal import in_terminal 

11from prompt_toolkit.completion import ( 

12 CompleteEvent, 

13 Completion, 

14 get_common_complete_suffix, 

15) 

16from prompt_toolkit.formatted_text import StyleAndTextTuples 

17from prompt_toolkit.key_binding.key_bindings import KeyBindings 

18from prompt_toolkit.key_binding.key_processor import KeyPressEvent 

19from prompt_toolkit.keys import Keys 

20from prompt_toolkit.utils import get_cwidth 

21 

22if TYPE_CHECKING: 

23 from prompt_toolkit.application import Application 

24 from prompt_toolkit.shortcuts import PromptSession 

25 

26__all__ = [ 

27 "generate_completions", 

28 "display_completions_like_readline", 

29] 

30 

31E = KeyPressEvent 

32 

33 

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

35 r""" 

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

37 second tab lists all the completions. 

38 """ 

39 b = event.current_buffer 

40 

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

42 if b.complete_state: 

43 b.complete_next() 

44 else: 

45 b.start_completion(insert_common_part=True) 

46 

47 

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

49 """ 

50 Key binding handler for readline-style tab completion. 

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

52 completions. 

53 

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

55 prompt in columns. 

56 

57 Usage:: 

58 

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

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

61 """ 

62 # Request completions. 

63 b = event.current_buffer 

64 if b.completer is None: 

65 return 

66 complete_event = CompleteEvent(completion_requested=True) 

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

68 

69 # Calculate the common suffix. 

70 common_suffix = get_common_complete_suffix(b.document, completions) 

71 

72 # One completion: insert it. 

73 if len(completions) == 1: 

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

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

76 # Multiple completions with common part. 

77 elif common_suffix: 

78 b.insert_text(common_suffix) 

79 # Otherwise: display all completions. 

80 elif completions: 

81 _display_completions_like_readline(event.app, completions) 

82 

83 

84def _display_completions_like_readline( 

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

86) -> asyncio.Task[None]: 

87 """ 

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

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

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

91 """ 

92 from prompt_toolkit.formatted_text import to_formatted_text 

93 from prompt_toolkit.shortcuts.prompt import create_confirm_session 

94 

95 # Get terminal dimensions. 

96 term_size = app.output.get_size() 

97 term_width = term_size.columns 

98 term_height = term_size.rows 

99 

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

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

102 # alphabetically column-wise.) 

103 max_compl_width = min( 

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

105 ) 

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

107 completions_per_page = column_count * (term_height - 1) 

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

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

110 

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

112 # Display completions. 

113 page_completions = completions[ 

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

115 ] 

116 

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

118 page_columns = [ 

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

120 for i in range(column_count) 

121 ] 

122 

123 result: StyleAndTextTuples = [] 

124 

125 for r in range(page_row_count): 

126 for c in range(column_count): 

127 try: 

128 completion = page_columns[c][r] 

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

130 completion.style or "" 

131 ) 

132 

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

134 

135 # Add padding. 

136 padding = max_compl_width - get_cwidth(completion.display_text) 

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

138 except IndexError: 

139 pass 

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

141 

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

143 

144 # User interaction through an application generator function. 

145 async def run_compl() -> None: 

146 "Coroutine." 

147 async with in_terminal(render_cli_done=True): 

148 if len(completions) > completions_per_page: 

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

150 confirm = await create_confirm_session( 

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

152 ).prompt_async() 

153 

154 if confirm: 

155 # Display pages. 

156 for page in range(page_count): 

157 display(page) 

158 

159 if page != page_count - 1: 

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

161 show_more = await _create_more_session( 

162 "--MORE--" 

163 ).prompt_async() 

164 

165 if not show_more: 

166 return 

167 else: 

168 app.output.flush() 

169 else: 

170 # Display all completions. 

171 display(0) 

172 

173 return app.create_background_task(run_compl()) 

174 

175 

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

177 """ 

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

179 """ 

180 from prompt_toolkit.shortcuts import PromptSession 

181 

182 bindings = KeyBindings() 

183 

184 @bindings.add(" ") 

185 @bindings.add("y") 

186 @bindings.add("Y") 

187 @bindings.add(Keys.ControlJ) 

188 @bindings.add(Keys.ControlM) 

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

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

191 event.app.exit(result=True) 

192 

193 @bindings.add("n") 

194 @bindings.add("N") 

195 @bindings.add("q") 

196 @bindings.add("Q") 

197 @bindings.add(Keys.ControlC) 

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

199 event.app.exit(result=False) 

200 

201 @bindings.add(Keys.Any) 

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

203 "Disable inserting of text." 

204 

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