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

296 statements  

1from __future__ import annotations 

2 

3import math 

4from itertools import zip_longest 

5from typing import TYPE_CHECKING, Callable, Iterable, Sequence, TypeVar, cast 

6from weakref import WeakKeyDictionary 

7 

8from prompt_toolkit.application.current import get_app 

9from prompt_toolkit.buffer import CompletionState 

10from prompt_toolkit.completion import Completion 

11from prompt_toolkit.data_structures import Point 

12from prompt_toolkit.filters import ( 

13 Condition, 

14 FilterOrBool, 

15 has_completions, 

16 is_done, 

17 to_filter, 

18) 

19from prompt_toolkit.formatted_text import ( 

20 StyleAndTextTuples, 

21 fragment_list_width, 

22 to_formatted_text, 

23) 

24from prompt_toolkit.key_binding.key_processor import KeyPressEvent 

25from prompt_toolkit.layout.utils import explode_text_fragments 

26from prompt_toolkit.mouse_events import MouseEvent, MouseEventType 

27from prompt_toolkit.utils import get_cwidth 

28 

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

30from .controls import GetLinePrefixCallable, UIContent, UIControl 

31from .dimension import Dimension 

32from .margins import ScrollbarMargin 

33 

34if TYPE_CHECKING: 

35 from prompt_toolkit.key_binding.key_bindings import ( 

36 KeyBindings, 

37 NotImplementedOrNone, 

38 ) 

39 

40 

41__all__ = [ 

42 "CompletionsMenu", 

43 "MultiColumnCompletionsMenu", 

44] 

45 

46E = KeyPressEvent 

47 

48 

49class CompletionsMenuControl(UIControl): 

50 """ 

51 Helper for drawing the complete menu to the screen. 

52 

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

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

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

56 middle most of the time. 

57 """ 

58 

59 # Preferred minimum size of the menu control. 

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

61 # of 1.) 

62 MIN_WIDTH = 7 

63 

64 def has_focus(self) -> bool: 

65 return False 

66 

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

68 complete_state = get_app().current_buffer.complete_state 

69 if complete_state: 

70 menu_width = self._get_menu_width(500, complete_state) 

71 menu_meta_width = self._get_menu_meta_width(500, complete_state) 

72 

73 return menu_width + menu_meta_width 

74 else: 

75 return 0 

76 

77 def preferred_height( 

78 self, 

79 width: int, 

80 max_available_height: int, 

81 wrap_lines: bool, 

82 get_line_prefix: GetLinePrefixCallable | None, 

83 ) -> int | None: 

84 complete_state = get_app().current_buffer.complete_state 

85 if complete_state: 

86 return len(complete_state.completions) 

87 else: 

88 return 0 

89 

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

91 """ 

92 Create a UIContent object for this control. 

93 """ 

94 complete_state = get_app().current_buffer.complete_state 

95 if complete_state: 

96 completions = complete_state.completions 

97 index = complete_state.complete_index # Can be None! 

98 

99 # Calculate width of completions menu. 

100 menu_width = self._get_menu_width(width, complete_state) 

101 menu_meta_width = self._get_menu_meta_width( 

102 width - menu_width, complete_state 

103 ) 

104 show_meta = self._show_meta(complete_state) 

105 

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

107 c = completions[i] 

108 is_current_completion = i == index 

109 result = _get_menu_item_fragments( 

110 c, is_current_completion, menu_width, space_after=True 

111 ) 

112 

113 if show_meta: 

114 result += self._get_menu_item_meta_fragments( 

115 c, is_current_completion, menu_meta_width 

116 ) 

117 return result 

118 

119 return UIContent( 

120 get_line=get_line, 

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

122 line_count=len(completions), 

123 ) 

124 

125 return UIContent() 

126 

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

128 """ 

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

130 """ 

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

132 

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

134 """ 

135 Return the width of the main column. 

136 """ 

137 return min( 

138 max_width, 

139 max( 

140 self.MIN_WIDTH, 

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

142 ), 

143 ) 

144 

145 def _get_menu_meta_width( 

146 self, max_width: int, complete_state: CompletionState 

147 ) -> int: 

148 """ 

149 Return the width of the meta column. 

150 """ 

151 

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

153 return get_cwidth(completion.display_meta_text) 

154 

155 if self._show_meta(complete_state): 

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

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

158 completions = complete_state.completions 

159 if len(completions) > 200: 

160 completions = completions[:200] 

161 

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

163 else: 

164 return 0 

165 

166 def _get_menu_item_meta_fragments( 

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

168 ) -> StyleAndTextTuples: 

169 if is_current_completion: 

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

171 else: 

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

173 

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

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

176 

177 return to_formatted_text( 

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

179 style=style_str, 

180 ) 

181 

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

183 """ 

184 Handle mouse events: clicking and scrolling. 

185 """ 

186 b = get_app().current_buffer 

187 

188 if mouse_event.event_type == MouseEventType.MOUSE_UP: 

189 # Select completion. 

190 b.go_to_completion(mouse_event.position.y) 

191 b.complete_state = None 

192 

193 elif mouse_event.event_type == MouseEventType.SCROLL_DOWN: 

194 # Scroll up. 

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

196 

197 elif mouse_event.event_type == MouseEventType.SCROLL_UP: 

198 # Scroll down. 

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

200 

201 return None 

202 

203 

204def _get_menu_item_fragments( 

205 completion: Completion, 

206 is_current_completion: bool, 

207 width: int, 

208 space_after: bool = False, 

209) -> StyleAndTextTuples: 

210 """ 

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

212 width. 

213 """ 

214 if is_current_completion: 

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

216 else: 

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

218 

219 text, tw = _trim_formatted_text( 

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

221 ) 

222 

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

224 

225 return to_formatted_text( 

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

227 style=style_str, 

228 ) 

229 

230 

231def _trim_formatted_text( 

232 formatted_text: StyleAndTextTuples, max_width: int 

233) -> tuple[StyleAndTextTuples, int]: 

234 """ 

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

236 Returns (text, width) tuple. 

237 """ 

238 width = fragment_list_width(formatted_text) 

239 

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

241 if width > max_width: 

242 result = [] # Text fragments. 

243 remaining_width = max_width - 3 

244 

245 for style_and_ch in explode_text_fragments(formatted_text): 

246 ch_width = get_cwidth(style_and_ch[1]) 

247 

248 if ch_width <= remaining_width: 

249 result.append(style_and_ch) 

250 remaining_width -= ch_width 

251 else: 

252 break 

253 

254 result.append(("", "...")) 

255 

256 return result, max_width - remaining_width 

257 else: 

258 return formatted_text, width 

259 

260 

261class CompletionsMenu(ConditionalContainer): 

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

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

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

265 def __init__( 

266 self, 

267 max_height: int | None = None, 

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

269 extra_filter: FilterOrBool = True, 

270 display_arrows: FilterOrBool = False, 

271 z_index: int = 10**8, 

272 ) -> None: 

273 extra_filter = to_filter(extra_filter) 

274 display_arrows = to_filter(display_arrows) 

275 

276 super().__init__( 

277 content=Window( 

278 content=CompletionsMenuControl(), 

279 width=Dimension(min=8), 

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

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

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

283 dont_extend_width=True, 

284 style="class:completion-menu", 

285 z_index=z_index, 

286 ), 

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

288 # returning the input. 

289 filter=extra_filter & has_completions & ~is_done, 

290 ) 

291 

292 

293class MultiColumnCompletionMenuControl(UIControl): 

294 """ 

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

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

297 arrow is shown on the left or right side. 

298 

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

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

301 rows until this value is reached. 

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

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

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

305 This results in less completions displayed and additional scrolling. 

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

307 widths are calculated, then the heights.) 

308 

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

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

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

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

313 reduce the amount of columns. 

314 """ 

315 

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

317 

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

319 assert min_rows >= 1 

320 

321 self.min_rows = min_rows 

322 self.suggested_max_column_width = suggested_max_column_width 

323 self.scroll = 0 

324 

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

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

327 # navigates through the completions. 

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

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

330 # `CompletionState` while loading.) 

331 self._column_width_for_completion_state: WeakKeyDictionary[ 

332 CompletionState, tuple[int, int] 

333 ] = WeakKeyDictionary() 

334 

335 # Info of last rendering. 

336 self._rendered_rows = 0 

337 self._rendered_columns = 0 

338 self._total_columns = 0 

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

340 self._render_left_arrow = False 

341 self._render_right_arrow = False 

342 self._render_width = 0 

343 

344 def reset(self) -> None: 

345 self.scroll = 0 

346 

347 def has_focus(self) -> bool: 

348 return False 

349 

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

351 """ 

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

353 as possible horizontally. 

354 """ 

355 complete_state = get_app().current_buffer.complete_state 

356 if complete_state is None: 

357 return 0 

358 

359 column_width = self._get_column_width(complete_state) 

360 result = int( 

361 column_width 

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

363 ) 

364 

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

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

367 # width. 

368 while ( 

369 result > column_width 

370 and result > max_available_width - self._required_margin 

371 ): 

372 result -= column_width 

373 return result + self._required_margin 

374 

375 def preferred_height( 

376 self, 

377 width: int, 

378 max_available_height: int, 

379 wrap_lines: bool, 

380 get_line_prefix: GetLinePrefixCallable | None, 

381 ) -> int | None: 

382 """ 

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

384 """ 

385 complete_state = get_app().current_buffer.complete_state 

386 if complete_state is None: 

387 return 0 

388 

389 column_width = self._get_column_width(complete_state) 

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

391 

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

393 

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

395 """ 

396 Create a UIContent object for this menu. 

397 """ 

398 complete_state = get_app().current_buffer.complete_state 

399 if complete_state is None: 

400 return UIContent() 

401 

402 column_width = self._get_column_width(complete_state) 

403 self._render_pos_to_completion = {} 

404 

405 _T = TypeVar("_T") 

406 

407 def grouper( 

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

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

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

411 args = [iter(iterable)] * n 

412 return zip_longest(fillvalue=fillvalue, *args) 

413 

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

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

416 return ( 

417 complete_state is not None 

418 and complete_state.complete_index is not None 

419 and c == complete_state.current_completion 

420 ) 

421 

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

423 # left and right arrow. 

424 HORIZONTAL_MARGIN_REQUIRED = 3 

425 

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

427 # the available width. 

428 column_width = min(width - HORIZONTAL_MARGIN_REQUIRED, column_width) 

429 

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

431 # some very wide entries, shrink it anyway. 

432 if column_width > self.suggested_max_column_width: 

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

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

435 column_width //= column_width // self.suggested_max_column_width 

436 

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

438 

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

440 rows_ = list(zip(*columns_)) 

441 

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

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

444 self.scroll = min( 

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

446 ) 

447 

448 render_left_arrow = self.scroll > 0 

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

450 

451 # Write completions to screen. 

452 fragments_for_line = [] 

453 

454 for row_index, row in enumerate(rows_): 

455 fragments: StyleAndTextTuples = [] 

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

457 

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

459 if render_left_arrow: 

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

461 elif render_right_arrow: 

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

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

464 fragments.append(("", " ")) 

465 

466 # Draw row content. 

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

468 if c is not None: 

469 fragments += _get_menu_item_fragments( 

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

471 ) 

472 

473 # Remember render position for mouse click handler. 

474 for x in range(column_width): 

475 self._render_pos_to_completion[ 

476 (column_index * column_width + x, row_index) 

477 ] = c 

478 else: 

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

480 

481 # Draw trailing padding for this row. 

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

483 if render_left_arrow or render_right_arrow: 

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

485 

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

487 if render_right_arrow: 

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

489 elif render_left_arrow: 

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

491 

492 # Add line. 

493 fragments_for_line.append( 

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

495 ) 

496 

497 self._rendered_rows = height 

498 self._rendered_columns = visible_columns 

499 self._total_columns = len(columns_) 

500 self._render_left_arrow = render_left_arrow 

501 self._render_right_arrow = render_right_arrow 

502 self._render_width = ( 

503 column_width * visible_columns + render_left_arrow + render_right_arrow + 1 

504 ) 

505 

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

507 return fragments_for_line[i] 

508 

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

510 

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

512 """ 

513 Return the width of each column. 

514 """ 

515 try: 

516 count, width = self._column_width_for_completion_state[completion_state] 

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

518 # Number of completions changed, recompute. 

519 raise KeyError 

520 return width 

521 except KeyError: 

522 result = ( 

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

524 + 1 

525 ) 

526 self._column_width_for_completion_state[completion_state] = ( 

527 len(completion_state.completions), 

528 result, 

529 ) 

530 return result 

531 

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

533 """ 

534 Handle scroll and click events. 

535 """ 

536 b = get_app().current_buffer 

537 

538 def scroll_left() -> None: 

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

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

541 

542 def scroll_right() -> None: 

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

544 self.scroll = min( 

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

546 ) 

547 

548 if mouse_event.event_type == MouseEventType.SCROLL_DOWN: 

549 scroll_right() 

550 

551 elif mouse_event.event_type == MouseEventType.SCROLL_UP: 

552 scroll_left() 

553 

554 elif mouse_event.event_type == MouseEventType.MOUSE_UP: 

555 x = mouse_event.position.x 

556 y = mouse_event.position.y 

557 

558 # Mouse click on left arrow. 

559 if x == 0: 

560 if self._render_left_arrow: 

561 scroll_left() 

562 

563 # Mouse click on right arrow. 

564 elif x == self._render_width - 1: 

565 if self._render_right_arrow: 

566 scroll_right() 

567 

568 # Mouse click on completion. 

569 else: 

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

571 if completion: 

572 b.apply_completion(completion) 

573 

574 return None 

575 

576 def get_key_bindings(self) -> KeyBindings: 

577 """ 

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

579 is displayed. 

580 """ 

581 from prompt_toolkit.key_binding.key_bindings import KeyBindings 

582 

583 kb = KeyBindings() 

584 

585 @Condition 

586 def filter() -> bool: 

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

588 app = get_app() 

589 complete_state = app.current_buffer.complete_state 

590 

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

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

593 return False 

594 

595 # This menu needs to be visible. 

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

597 

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

599 buff = get_app().current_buffer 

600 complete_state = buff.complete_state 

601 

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

603 # Calculate new complete index. 

604 new_index = complete_state.complete_index 

605 if right: 

606 new_index += self._rendered_rows 

607 else: 

608 new_index -= self._rendered_rows 

609 

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

611 buff.go_to_completion(new_index) 

612 

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

614 # never be focussed. 

615 

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

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

618 move() 

619 

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

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

622 move(True) 

623 

624 return kb 

625 

626 

627class MultiColumnCompletionsMenu(HSplit): 

628 """ 

629 Container that displays the completions in several columns. 

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

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

632 """ 

633 

634 def __init__( 

635 self, 

636 min_rows: int = 3, 

637 suggested_max_column_width: int = 30, 

638 show_meta: FilterOrBool = True, 

639 extra_filter: FilterOrBool = True, 

640 z_index: int = 10**8, 

641 ) -> None: 

642 show_meta = to_filter(show_meta) 

643 extra_filter = to_filter(extra_filter) 

644 

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

646 # we are returning the input. 

647 full_filter = extra_filter & has_completions & ~is_done 

648 

649 @Condition 

650 def any_completion_has_meta() -> bool: 

651 complete_state = get_app().current_buffer.complete_state 

652 return complete_state is not None and any( 

653 c.display_meta for c in complete_state.completions 

654 ) 

655 

656 # Create child windows. 

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

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

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

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

661 # generated content. 

662 completions_window = ConditionalContainer( 

663 content=Window( 

664 content=MultiColumnCompletionMenuControl( 

665 min_rows=min_rows, 

666 suggested_max_column_width=suggested_max_column_width, 

667 ), 

668 width=Dimension(min=8), 

669 height=Dimension(min=1), 

670 ), 

671 filter=full_filter, 

672 ) 

673 

674 meta_window = ConditionalContainer( 

675 content=Window(content=_SelectedCompletionMetaControl()), 

676 filter=full_filter & show_meta & any_completion_has_meta, 

677 ) 

678 

679 # Initialize split. 

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

681 

682 

683class _SelectedCompletionMetaControl(UIControl): 

684 """ 

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

686 """ 

687 

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

689 """ 

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

691 

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

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

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

695 """ 

696 app = get_app() 

697 if app.current_buffer.complete_state: 

698 state = app.current_buffer.complete_state 

699 

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

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

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

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

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

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

706 # space as needed. 

707 return max_available_width 

708 

709 return 2 + max( 

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

711 ) 

712 else: 

713 return 0 

714 

715 def preferred_height( 

716 self, 

717 width: int, 

718 max_available_height: int, 

719 wrap_lines: bool, 

720 get_line_prefix: GetLinePrefixCallable | None, 

721 ) -> int | None: 

722 return 1 

723 

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

725 fragments = self._get_text_fragments() 

726 

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

728 return fragments 

729 

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

731 

732 def _get_text_fragments(self) -> StyleAndTextTuples: 

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

734 state = get_app().current_buffer.complete_state 

735 

736 if ( 

737 state 

738 and state.current_completion 

739 and state.current_completion.display_meta_text 

740 ): 

741 return to_formatted_text( 

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

743 + state.current_completion.display_meta 

744 + [("", " ")], 

745 style=style, 

746 ) 

747 

748 return []