Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/prompt_toolkit/layout/menus.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

297 statements  

1from __future__ import annotations 

2 

3import math 

4from collections.abc import Callable, Iterable, Sequence 

5from itertools import zip_longest 

6from typing import TYPE_CHECKING, TypeVar, cast 

7from weakref import WeakKeyDictionary 

8 

9from prompt_toolkit.application.current import get_app 

10from prompt_toolkit.buffer import CompletionState 

11from prompt_toolkit.completion import Completion 

12from prompt_toolkit.data_structures import Point 

13from prompt_toolkit.filters import ( 

14 Condition, 

15 FilterOrBool, 

16 has_completions, 

17 is_done, 

18 to_filter, 

19) 

20from prompt_toolkit.formatted_text import ( 

21 StyleAndTextTuples, 

22 fragment_list_width, 

23 to_formatted_text, 

24) 

25from prompt_toolkit.key_binding.key_processor import KeyPressEvent 

26from prompt_toolkit.layout.utils import explode_text_fragments 

27from prompt_toolkit.mouse_events import MouseEvent, MouseEventType 

28from prompt_toolkit.utils import get_cwidth 

29 

30from .containers import ConditionalContainer, HSplit, ScrollOffsets, Window 

31from .controls import GetLinePrefixCallable, UIContent, UIControl 

32from .dimension import Dimension 

33from .margins import ScrollbarMargin 

34 

35if TYPE_CHECKING: 

36 from prompt_toolkit.key_binding.key_bindings import ( 

37 KeyBindings, 

38 NotImplementedOrNone, 

39 ) 

40 

41 

42__all__ = [ 

43 "CompletionsMenu", 

44 "MultiColumnCompletionsMenu", 

45] 

46 

47E = KeyPressEvent 

48 

49 

50class CompletionsMenuControl(UIControl): 

51 """ 

52 Helper for drawing the complete menu to the screen. 

53 

54 :param scroll_offset: Number (integer) representing the preferred amount of 

55 completions to be displayed before and after the current one. When this 

56 is a very high number, the current completion will be shown in the 

57 middle most of the time. 

58 """ 

59 

60 # Preferred minimum size of the menu control. 

61 # The CompletionsMenu class defines a width of 8, and there is a scrollbar 

62 # of 1.) 

63 MIN_WIDTH = 7 

64 

65 def has_focus(self) -> bool: 

66 return False 

67 

68 def preferred_width(self, max_available_width: int) -> int | None: 

69 complete_state = get_app().current_buffer.complete_state 

70 if complete_state: 

71 menu_width = self._get_menu_width(500, complete_state) 

72 menu_meta_width = self._get_menu_meta_width(500, complete_state) 

73 

74 return menu_width + menu_meta_width 

75 else: 

76 return 0 

77 

78 def preferred_height( 

79 self, 

80 width: int, 

81 max_available_height: int, 

82 wrap_lines: bool, 

83 get_line_prefix: GetLinePrefixCallable | None, 

84 ) -> int | None: 

85 complete_state = get_app().current_buffer.complete_state 

86 if complete_state: 

87 return len(complete_state.completions) 

88 else: 

89 return 0 

90 

91 def create_content(self, width: int, height: int) -> UIContent: 

92 """ 

93 Create a UIContent object for this control. 

94 """ 

95 complete_state = get_app().current_buffer.complete_state 

96 if complete_state: 

97 completions = complete_state.completions 

98 index = complete_state.complete_index # Can be None! 

99 

100 # Calculate width of completions menu. 

101 menu_width = self._get_menu_width(width, complete_state) 

102 menu_meta_width = self._get_menu_meta_width( 

103 width - menu_width, complete_state 

104 ) 

105 show_meta = self._show_meta(complete_state) 

106 

107 def get_line(i: int) -> StyleAndTextTuples: 

108 c = completions[i] 

109 is_current_completion = i == index 

110 result = _get_menu_item_fragments( 

111 c, is_current_completion, menu_width, space_after=True 

112 ) 

113 

114 if show_meta: 

115 result += self._get_menu_item_meta_fragments( 

116 c, is_current_completion, menu_meta_width 

117 ) 

118 return result 

119 

120 return UIContent( 

121 get_line=get_line, 

122 cursor_position=Point(x=0, y=index or 0), 

123 line_count=len(completions), 

124 ) 

125 

126 return UIContent() 

127 

128 def _show_meta(self, complete_state: CompletionState) -> bool: 

129 """ 

130 Return ``True`` if we need to show a column with meta information. 

131 """ 

132 return any(c.display_meta_text for c in complete_state.completions) 

133 

134 def _get_menu_width(self, max_width: int, complete_state: CompletionState) -> int: 

135 """ 

136 Return the width of the main column. 

137 """ 

138 return min( 

139 max_width, 

140 max( 

141 self.MIN_WIDTH, 

142 max(get_cwidth(c.display_text) for c in complete_state.completions) + 2, 

143 ), 

144 ) 

145 

146 def _get_menu_meta_width( 

147 self, max_width: int, complete_state: CompletionState 

148 ) -> int: 

149 """ 

150 Return the width of the meta column. 

151 """ 

152 

153 def meta_width(completion: Completion) -> int: 

154 return get_cwidth(completion.display_meta_text) 

155 

156 if self._show_meta(complete_state): 

157 # If the amount of completions is over 200, compute the width based 

158 # on the first 200 completions, otherwise this can be very slow. 

159 completions = complete_state.completions 

160 if len(completions) > 200: 

161 completions = completions[:200] 

162 

163 return min(max_width, max(meta_width(c) for c in completions) + 2) 

164 else: 

165 return 0 

166 

167 def _get_menu_item_meta_fragments( 

168 self, completion: Completion, is_current_completion: bool, width: int 

169 ) -> StyleAndTextTuples: 

170 if is_current_completion: 

171 style_str = "class:completion-menu.meta.completion.current" 

172 else: 

173 style_str = "class:completion-menu.meta.completion" 

174 

175 text, tw = _trim_formatted_text(completion.display_meta, width - 2) 

176 padding = " " * (width - 1 - tw) 

177 

178 return to_formatted_text( 

179 cast(StyleAndTextTuples, []) + [("", " ")] + text + [("", padding)], 

180 style=style_str, 

181 ) 

182 

183 def mouse_handler(self, mouse_event: MouseEvent) -> NotImplementedOrNone: 

184 """ 

185 Handle mouse events: clicking and scrolling. 

186 """ 

187 b = get_app().current_buffer 

188 

189 if mouse_event.event_type == MouseEventType.MOUSE_UP: 

190 # Select completion. 

191 b.go_to_completion(mouse_event.position.y) 

192 b.complete_state = None 

193 

194 elif mouse_event.event_type == MouseEventType.SCROLL_DOWN: 

195 # Scroll up. 

196 b.complete_next(count=3, disable_wrap_around=True) 

197 

198 elif mouse_event.event_type == MouseEventType.SCROLL_UP: 

199 # Scroll down. 

200 b.complete_previous(count=3, disable_wrap_around=True) 

201 

202 return None 

203 

204 

205def _get_menu_item_fragments( 

206 completion: Completion, 

207 is_current_completion: bool, 

208 width: int, 

209 space_after: bool = False, 

210) -> StyleAndTextTuples: 

211 """ 

212 Get the style/text tuples for a menu item, styled and trimmed to the given 

213 width. 

214 """ 

215 if is_current_completion: 

216 style_str = f"class:completion-menu.completion.current {completion.style} {completion.selected_style}" 

217 else: 

218 style_str = "class:completion-menu.completion " + completion.style 

219 

220 text, tw = _trim_formatted_text( 

221 completion.display, (width - 2 if space_after else width - 1) 

222 ) 

223 

224 padding = " " * (width - 1 - tw) 

225 

226 return to_formatted_text( 

227 cast(StyleAndTextTuples, []) + [("", " ")] + text + [("", padding)], 

228 style=style_str, 

229 ) 

230 

231 

232def _trim_formatted_text( 

233 formatted_text: StyleAndTextTuples, max_width: int 

234) -> tuple[StyleAndTextTuples, int]: 

235 """ 

236 Trim the text to `max_width`, append dots when the text is too long. 

237 Returns (text, width) tuple. 

238 """ 

239 width = fragment_list_width(formatted_text) 

240 

241 # When the text is too wide, trim it. 

242 if width > max_width: 

243 result = [] # Text fragments. 

244 remaining_width = max_width - 3 

245 

246 for style_and_ch in explode_text_fragments(formatted_text): 

247 ch_width = get_cwidth(style_and_ch[1]) 

248 

249 if ch_width <= remaining_width: 

250 result.append(style_and_ch) 

251 remaining_width -= ch_width 

252 else: 

253 break 

254 

255 result.append(("", "...")) 

256 

257 return result, max_width - remaining_width 

258 else: 

259 return formatted_text, width 

260 

261 

262class CompletionsMenu(ConditionalContainer): 

263 # NOTE: We use a pretty big z_index by default. Menus are supposed to be 

264 # above anything else. We also want to make sure that the content is 

265 # visible at the point where we draw this menu. 

266 def __init__( 

267 self, 

268 max_height: int | None = None, 

269 scroll_offset: int | Callable[[], int] = 0, 

270 extra_filter: FilterOrBool = True, 

271 display_arrows: FilterOrBool = False, 

272 z_index: int = 10**8, 

273 ) -> None: 

274 extra_filter = to_filter(extra_filter) 

275 display_arrows = to_filter(display_arrows) 

276 

277 super().__init__( 

278 content=Window( 

279 content=CompletionsMenuControl(), 

280 width=Dimension(min=8), 

281 height=Dimension(min=1, max=max_height), 

282 scroll_offsets=ScrollOffsets(top=scroll_offset, bottom=scroll_offset), 

283 right_margins=[ScrollbarMargin(display_arrows=display_arrows)], 

284 dont_extend_width=True, 

285 style="class:completion-menu", 

286 z_index=z_index, 

287 ), 

288 # Show when there are completions but not at the point we are 

289 # returning the input. 

290 filter=extra_filter & has_completions & ~is_done, 

291 ) 

292 

293 

294class MultiColumnCompletionMenuControl(UIControl): 

295 """ 

296 Completion menu that displays all the completions in several columns. 

297 When there are more completions than space for them to be displayed, an 

298 arrow is shown on the left or right side. 

299 

300 `min_rows` indicates how many rows will be available in any possible case. 

301 When this is larger than one, it will try to use less columns and more 

302 rows until this value is reached. 

303 Be careful passing in a too big value, if less than the given amount of 

304 rows are available, more columns would have been required, but 

305 `preferred_width` doesn't know about that and reports a too small value. 

306 This results in less completions displayed and additional scrolling. 

307 (It's a limitation of how the layout engine currently works: first the 

308 widths are calculated, then the heights.) 

309 

310 :param suggested_max_column_width: The suggested max width of a column. 

311 The column can still be bigger than this, but if there is place for two 

312 columns of this width, we will display two columns. This to avoid that 

313 if there is one very wide completion, that it doesn't significantly 

314 reduce the amount of columns. 

315 """ 

316 

317 _required_margin = 3 # One extra padding on the right + space for arrows. 

318 

319 def __init__(self, min_rows: int = 3, suggested_max_column_width: int = 30) -> None: 

320 assert min_rows >= 1 

321 

322 self.min_rows = min_rows 

323 self.suggested_max_column_width = suggested_max_column_width 

324 self.scroll = 0 

325 

326 # Cache for column width computations. This computation is not cheap, 

327 # so we don't want to do it over and over again while the user 

328 # navigates through the completions. 

329 # (map `completion_state` to `(completion_count, width)`. We remember 

330 # the count, because a completer can add new completions to the 

331 # `CompletionState` while loading.) 

332 self._column_width_for_completion_state: WeakKeyDictionary[ 

333 CompletionState, tuple[int, int] 

334 ] = WeakKeyDictionary() 

335 

336 # Info of last rendering. 

337 self._rendered_rows = 0 

338 self._rendered_columns = 0 

339 self._total_columns = 0 

340 self._render_pos_to_completion: dict[tuple[int, int], Completion] = {} 

341 self._render_left_arrow = False 

342 self._render_right_arrow = False 

343 self._render_width = 0 

344 

345 def reset(self) -> None: 

346 self.scroll = 0 

347 

348 def has_focus(self) -> bool: 

349 return False 

350 

351 def preferred_width(self, max_available_width: int) -> int | None: 

352 """ 

353 Preferred width: prefer to use at least min_rows, but otherwise as much 

354 as possible horizontally. 

355 """ 

356 complete_state = get_app().current_buffer.complete_state 

357 if complete_state is None: 

358 return 0 

359 

360 column_width = self._get_column_width(complete_state) 

361 result = int( 

362 column_width 

363 * math.ceil(len(complete_state.completions) / float(self.min_rows)) 

364 ) 

365 

366 # When the desired width is still more than the maximum available, 

367 # reduce by removing columns until we are less than the available 

368 # width. 

369 while ( 

370 result > column_width 

371 and result > max_available_width - self._required_margin 

372 ): 

373 result -= column_width 

374 return result + self._required_margin 

375 

376 def preferred_height( 

377 self, 

378 width: int, 

379 max_available_height: int, 

380 wrap_lines: bool, 

381 get_line_prefix: GetLinePrefixCallable | None, 

382 ) -> int | None: 

383 """ 

384 Preferred height: as much as needed in order to display all the completions. 

385 """ 

386 complete_state = get_app().current_buffer.complete_state 

387 if complete_state is None: 

388 return 0 

389 

390 column_width = self._get_column_width(complete_state) 

391 column_count = max(1, (width - self._required_margin) // column_width) 

392 

393 return int(math.ceil(len(complete_state.completions) / float(column_count))) 

394 

395 def create_content(self, width: int, height: int) -> UIContent: 

396 """ 

397 Create a UIContent object for this menu. 

398 """ 

399 complete_state = get_app().current_buffer.complete_state 

400 if complete_state is None: 

401 return UIContent() 

402 

403 column_width = self._get_column_width(complete_state) 

404 self._render_pos_to_completion = {} 

405 

406 _T = TypeVar("_T") 

407 

408 def grouper( 

409 n: int, iterable: Iterable[_T], fillvalue: _T | None = None 

410 ) -> Iterable[Sequence[_T | None]]: 

411 "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx" 

412 args = [iter(iterable)] * n 

413 return zip_longest(fillvalue=fillvalue, *args) 

414 

415 def is_current_completion(completion: Completion) -> bool: 

416 "Returns True when this completion is the currently selected one." 

417 return ( 

418 complete_state is not None 

419 and complete_state.complete_index is not None 

420 and c == complete_state.current_completion 

421 ) 

422 

423 # Space required outside of the regular columns, for displaying the 

424 # left and right arrow. 

425 HORIZONTAL_MARGIN_REQUIRED = 3 

426 

427 # There should be at least one column, but it cannot be wider than 

428 # the available width. 

429 column_width = min(width - HORIZONTAL_MARGIN_REQUIRED, column_width) 

430 

431 # However, when the columns tend to be very wide, because there are 

432 # some very wide entries, shrink it anyway. 

433 if column_width > self.suggested_max_column_width: 

434 # `column_width` can still be bigger that `suggested_max_column_width`, 

435 # but if there is place for two columns, we divide by two. 

436 column_width //= column_width // self.suggested_max_column_width 

437 

438 visible_columns = max(1, (width - self._required_margin) // column_width) 

439 

440 columns_ = list(grouper(height, complete_state.completions)) 

441 rows_ = list(zip(*columns_)) 

442 

443 # Make sure the current completion is always visible: update scroll offset. 

444 selected_column = (complete_state.complete_index or 0) // height 

445 self.scroll = min( 

446 selected_column, max(self.scroll, selected_column - visible_columns + 1) 

447 ) 

448 

449 render_left_arrow = self.scroll > 0 

450 render_right_arrow = self.scroll < len(rows_[0]) - visible_columns 

451 

452 # Write completions to screen. 

453 fragments_for_line = [] 

454 

455 for row_index, row in enumerate(rows_): 

456 fragments: StyleAndTextTuples = [] 

457 middle_row = row_index == len(rows_) // 2 

458 

459 # Draw left arrow if we have hidden completions on the left. 

460 if render_left_arrow: 

461 fragments.append(("class:scrollbar", "<" if middle_row else " ")) 

462 elif render_right_arrow: 

463 # Reserve one column empty space. (If there is a right 

464 # arrow right now, there can be a left arrow as well.) 

465 fragments.append(("", " ")) 

466 

467 # Draw row content. 

468 for column_index, c in enumerate(row[self.scroll :][:visible_columns]): 

469 if c is not None: 

470 fragments += _get_menu_item_fragments( 

471 c, is_current_completion(c), column_width, space_after=False 

472 ) 

473 

474 # Remember render position for mouse click handler. 

475 for x in range(column_width): 

476 self._render_pos_to_completion[ 

477 (column_index * column_width + x, row_index) 

478 ] = c 

479 else: 

480 fragments.append(("class:completion", " " * column_width)) 

481 

482 # Draw trailing padding for this row. 

483 # (_get_menu_item_fragments only returns padding on the left.) 

484 if render_left_arrow or render_right_arrow: 

485 fragments.append(("class:completion", " ")) 

486 

487 # Draw right arrow if we have hidden completions on the right. 

488 if render_right_arrow: 

489 fragments.append(("class:scrollbar", ">" if middle_row else " ")) 

490 elif render_left_arrow: 

491 fragments.append(("class:completion", " ")) 

492 

493 # Add line. 

494 fragments_for_line.append( 

495 to_formatted_text(fragments, style="class:completion-menu") 

496 ) 

497 

498 self._rendered_rows = height 

499 self._rendered_columns = visible_columns 

500 self._total_columns = len(columns_) 

501 self._render_left_arrow = render_left_arrow 

502 self._render_right_arrow = render_right_arrow 

503 self._render_width = ( 

504 column_width * visible_columns + render_left_arrow + render_right_arrow + 1 

505 ) 

506 

507 def get_line(i: int) -> StyleAndTextTuples: 

508 return fragments_for_line[i] 

509 

510 return UIContent(get_line=get_line, line_count=len(rows_)) 

511 

512 def _get_column_width(self, completion_state: CompletionState) -> int: 

513 """ 

514 Return the width of each column. 

515 """ 

516 try: 

517 count, width = self._column_width_for_completion_state[completion_state] 

518 if count != len(completion_state.completions): 

519 # Number of completions changed, recompute. 

520 raise KeyError 

521 return width 

522 except KeyError: 

523 result = ( 

524 max(get_cwidth(c.display_text) for c in completion_state.completions) 

525 + 1 

526 ) 

527 self._column_width_for_completion_state[completion_state] = ( 

528 len(completion_state.completions), 

529 result, 

530 ) 

531 return result 

532 

533 def mouse_handler(self, mouse_event: MouseEvent) -> NotImplementedOrNone: 

534 """ 

535 Handle scroll and click events. 

536 """ 

537 b = get_app().current_buffer 

538 

539 def scroll_left() -> None: 

540 b.complete_previous(count=self._rendered_rows, disable_wrap_around=True) 

541 self.scroll = max(0, self.scroll - 1) 

542 

543 def scroll_right() -> None: 

544 b.complete_next(count=self._rendered_rows, disable_wrap_around=True) 

545 self.scroll = min( 

546 self._total_columns - self._rendered_columns, self.scroll + 1 

547 ) 

548 

549 if mouse_event.event_type == MouseEventType.SCROLL_DOWN: 

550 scroll_right() 

551 

552 elif mouse_event.event_type == MouseEventType.SCROLL_UP: 

553 scroll_left() 

554 

555 elif mouse_event.event_type == MouseEventType.MOUSE_UP: 

556 x = mouse_event.position.x 

557 y = mouse_event.position.y 

558 

559 # Mouse click on left arrow. 

560 if x == 0: 

561 if self._render_left_arrow: 

562 scroll_left() 

563 

564 # Mouse click on right arrow. 

565 elif x == self._render_width - 1: 

566 if self._render_right_arrow: 

567 scroll_right() 

568 

569 # Mouse click on completion. 

570 else: 

571 completion = self._render_pos_to_completion.get((x, y)) 

572 if completion: 

573 b.apply_completion(completion) 

574 

575 return None 

576 

577 def get_key_bindings(self) -> KeyBindings: 

578 """ 

579 Expose key bindings that handle the left/right arrow keys when the menu 

580 is displayed. 

581 """ 

582 from prompt_toolkit.key_binding.key_bindings import KeyBindings 

583 

584 kb = KeyBindings() 

585 

586 @Condition 

587 def filter() -> bool: 

588 "Only handle key bindings if this menu is visible." 

589 app = get_app() 

590 complete_state = app.current_buffer.complete_state 

591 

592 # There need to be completions, and one needs to be selected. 

593 if complete_state is None or complete_state.complete_index is None: 

594 return False 

595 

596 # This menu needs to be visible. 

597 return any(window.content == self for window in app.layout.visible_windows) 

598 

599 def move(right: bool = False) -> None: 

600 buff = get_app().current_buffer 

601 complete_state = buff.complete_state 

602 

603 if complete_state is not None and complete_state.complete_index is not None: 

604 # Calculate new complete index. 

605 new_index = complete_state.complete_index 

606 if right: 

607 new_index += self._rendered_rows 

608 else: 

609 new_index -= self._rendered_rows 

610 

611 if 0 <= new_index < len(complete_state.completions): 

612 buff.go_to_completion(new_index) 

613 

614 # NOTE: the is_global is required because the completion menu will 

615 # never be focussed. 

616 

617 @kb.add("left", is_global=True, filter=filter) 

618 def _left(event: E) -> None: 

619 move() 

620 

621 @kb.add("right", is_global=True, filter=filter) 

622 def _right(event: E) -> None: 

623 move(True) 

624 

625 return kb 

626 

627 

628class MultiColumnCompletionsMenu(HSplit): 

629 """ 

630 Container that displays the completions in several columns. 

631 When `show_meta` (a :class:`~prompt_toolkit.filters.Filter`) evaluates 

632 to True, it shows the meta information at the bottom. 

633 """ 

634 

635 def __init__( 

636 self, 

637 min_rows: int = 3, 

638 suggested_max_column_width: int = 30, 

639 show_meta: FilterOrBool = True, 

640 extra_filter: FilterOrBool = True, 

641 z_index: int = 10**8, 

642 ) -> None: 

643 show_meta = to_filter(show_meta) 

644 extra_filter = to_filter(extra_filter) 

645 

646 # Display filter: show when there are completions but not at the point 

647 # we are returning the input. 

648 full_filter = extra_filter & has_completions & ~is_done 

649 

650 @Condition 

651 def any_completion_has_meta() -> bool: 

652 complete_state = get_app().current_buffer.complete_state 

653 return complete_state is not None and any( 

654 c.display_meta for c in complete_state.completions 

655 ) 

656 

657 # Create child windows. 

658 # NOTE: We don't set style='class:completion-menu' to the 

659 # `MultiColumnCompletionMenuControl`, because this is used in a 

660 # Float that is made transparent, and the size of the control 

661 # doesn't always correspond exactly with the size of the 

662 # generated content. 

663 completions_window = ConditionalContainer( 

664 content=Window( 

665 content=MultiColumnCompletionMenuControl( 

666 min_rows=min_rows, 

667 suggested_max_column_width=suggested_max_column_width, 

668 ), 

669 width=Dimension(min=8), 

670 height=Dimension(min=1), 

671 ), 

672 filter=full_filter, 

673 ) 

674 

675 meta_window = ConditionalContainer( 

676 content=Window(content=_SelectedCompletionMetaControl()), 

677 filter=full_filter & show_meta & any_completion_has_meta, 

678 ) 

679 

680 # Initialize split. 

681 super().__init__([completions_window, meta_window], z_index=z_index) 

682 

683 

684class _SelectedCompletionMetaControl(UIControl): 

685 """ 

686 Control that shows the meta information of the selected completion. 

687 """ 

688 

689 def preferred_width(self, max_available_width: int) -> int | None: 

690 """ 

691 Report the width of the longest meta text as the preferred width of this control. 

692 

693 It could be that we use less width, but this way, we're sure that the 

694 layout doesn't change when we select another completion (E.g. that 

695 completions are suddenly shown in more or fewer columns.) 

696 """ 

697 app = get_app() 

698 if app.current_buffer.complete_state: 

699 state = app.current_buffer.complete_state 

700 

701 if len(state.completions) >= 30: 

702 # When there are many completions, calling `get_cwidth` for 

703 # every `display_meta_text` is too expensive. In this case, 

704 # just return the max available width. There will be enough 

705 # columns anyway so that the whole screen is filled with 

706 # completions and `create_content` will then take up as much 

707 # space as needed. 

708 return max_available_width 

709 

710 return 2 + max( 

711 get_cwidth(c.display_meta_text) for c in state.completions[:100] 

712 ) 

713 else: 

714 return 0 

715 

716 def preferred_height( 

717 self, 

718 width: int, 

719 max_available_height: int, 

720 wrap_lines: bool, 

721 get_line_prefix: GetLinePrefixCallable | None, 

722 ) -> int | None: 

723 return 1 

724 

725 def create_content(self, width: int, height: int) -> UIContent: 

726 fragments = self._get_text_fragments() 

727 

728 def get_line(i: int) -> StyleAndTextTuples: 

729 return fragments 

730 

731 return UIContent(get_line=get_line, line_count=1 if fragments else 0) 

732 

733 def _get_text_fragments(self) -> StyleAndTextTuples: 

734 style = "class:completion-menu.multi-column-meta" 

735 state = get_app().current_buffer.complete_state 

736 

737 if ( 

738 state 

739 and state.current_completion 

740 and state.current_completion.display_meta_text 

741 ): 

742 return to_formatted_text( 

743 cast(StyleAndTextTuples, [("", " ")]) 

744 + state.current_completion.display_meta 

745 + [("", " ")], 

746 style=style, 

747 ) 

748 

749 return []