Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/prompt_toolkit/renderer.py: 14%

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

346 statements  

1""" 

2Renders the command line on the console. 

3(Redraws parts of the input line that were changed.) 

4""" 

5 

6from __future__ import annotations 

7 

8from asyncio import FIRST_COMPLETED, Future, ensure_future, sleep, wait 

9from collections import deque 

10from enum import Enum 

11from typing import TYPE_CHECKING, Any, Callable, Dict, Hashable 

12 

13from prompt_toolkit.application.current import get_app 

14from prompt_toolkit.cursor_shapes import CursorShape 

15from prompt_toolkit.data_structures import Point, Size 

16from prompt_toolkit.filters import FilterOrBool, to_filter 

17from prompt_toolkit.formatted_text import AnyFormattedText, to_formatted_text 

18from prompt_toolkit.layout.mouse_handlers import MouseHandlers 

19from prompt_toolkit.layout.screen import Char, Screen, WritePosition 

20from prompt_toolkit.output import ColorDepth, Output 

21from prompt_toolkit.styles import ( 

22 Attrs, 

23 BaseStyle, 

24 DummyStyleTransformation, 

25 StyleTransformation, 

26) 

27 

28if TYPE_CHECKING: 

29 from prompt_toolkit.application import Application 

30 from prompt_toolkit.layout.layout import Layout 

31 

32 

33__all__ = [ 

34 "Renderer", 

35 "print_formatted_text", 

36] 

37 

38 

39def _output_screen_diff( 

40 app: Application[Any], 

41 output: Output, 

42 screen: Screen, 

43 current_pos: Point, 

44 color_depth: ColorDepth, 

45 previous_screen: Screen | None, 

46 last_style: str | None, 

47 is_done: bool, # XXX: drop is_done 

48 full_screen: bool, 

49 attrs_for_style_string: _StyleStringToAttrsCache, 

50 style_string_has_style: _StyleStringHasStyleCache, 

51 size: Size, 

52 previous_width: int, 

53) -> tuple[Point, str | None]: 

54 """ 

55 Render the diff between this screen and the previous screen. 

56 

57 This takes two `Screen` instances. The one that represents the output like 

58 it was during the last rendering and one that represents the current 

59 output raster. Looking at these two `Screen` instances, this function will 

60 render the difference by calling the appropriate methods of the `Output` 

61 object that only paint the changes to the terminal. 

62 

63 This is some performance-critical code which is heavily optimized. 

64 Don't change things without profiling first. 

65 

66 :param current_pos: Current cursor position. 

67 :param last_style: The style string, used for drawing the last drawn 

68 character. (Color/attributes.) 

69 :param attrs_for_style_string: :class:`._StyleStringToAttrsCache` instance. 

70 :param width: The width of the terminal. 

71 :param previous_width: The width of the terminal during the last rendering. 

72 """ 

73 width, height = size.columns, size.rows 

74 

75 #: Variable for capturing the output. 

76 write = output.write 

77 write_raw = output.write_raw 

78 

79 # Create locals for the most used output methods. 

80 # (Save expensive attribute lookups.) 

81 _output_set_attributes = output.set_attributes 

82 _output_reset_attributes = output.reset_attributes 

83 _output_cursor_forward = output.cursor_forward 

84 _output_cursor_up = output.cursor_up 

85 _output_cursor_backward = output.cursor_backward 

86 

87 # Hide cursor before rendering. (Avoid flickering.) 

88 output.hide_cursor() 

89 

90 def reset_attributes() -> None: 

91 "Wrapper around Output.reset_attributes." 

92 nonlocal last_style 

93 _output_reset_attributes() 

94 last_style = None # Forget last char after resetting attributes. 

95 

96 def move_cursor(new: Point) -> Point: 

97 "Move cursor to this `new` point. Returns the given Point." 

98 current_x, current_y = current_pos.x, current_pos.y 

99 

100 if new.y > current_y: 

101 # Use newlines instead of CURSOR_DOWN, because this might add new lines. 

102 # CURSOR_DOWN will never create new lines at the bottom. 

103 # Also reset attributes, otherwise the newline could draw a 

104 # background color. 

105 reset_attributes() 

106 write("\r\n" * (new.y - current_y)) 

107 current_x = 0 

108 _output_cursor_forward(new.x) 

109 return new 

110 elif new.y < current_y: 

111 _output_cursor_up(current_y - new.y) 

112 

113 if current_x >= width - 1: 

114 write("\r") 

115 _output_cursor_forward(new.x) 

116 elif new.x < current_x or current_x >= width - 1: 

117 _output_cursor_backward(current_x - new.x) 

118 elif new.x > current_x: 

119 _output_cursor_forward(new.x - current_x) 

120 

121 return new 

122 

123 def output_char(char: Char) -> None: 

124 """ 

125 Write the output of this character. 

126 """ 

127 nonlocal last_style 

128 

129 # If the last printed character has the same style, don't output the 

130 # style again. 

131 if last_style == char.style: 

132 write(char.char) 

133 else: 

134 # Look up `Attr` for this style string. Only set attributes if different. 

135 # (Two style strings can still have the same formatting.) 

136 # Note that an empty style string can have formatting that needs to 

137 # be applied, because of style transformations. 

138 new_attrs = attrs_for_style_string[char.style] 

139 if not last_style or new_attrs != attrs_for_style_string[last_style]: 

140 _output_set_attributes(new_attrs, color_depth) 

141 

142 write(char.char) 

143 last_style = char.style 

144 

145 def get_max_column_index(row: dict[int, Char]) -> int: 

146 """ 

147 Return max used column index, ignoring whitespace (without style) at 

148 the end of the line. This is important for people that copy/paste 

149 terminal output. 

150 

151 There are two reasons we are sometimes seeing whitespace at the end: 

152 - `BufferControl` adds a trailing space to each line, because it's a 

153 possible cursor position, so that the line wrapping won't change if 

154 the cursor position moves around. 

155 - The `Window` adds a style class to the current line for highlighting 

156 (cursor-line). 

157 """ 

158 numbers = ( 

159 index 

160 for index, cell in row.items() 

161 if cell.char != " " or style_string_has_style[cell.style] 

162 ) 

163 return max(numbers, default=0) 

164 

165 # Render for the first time: reset styling. 

166 if not previous_screen: 

167 reset_attributes() 

168 

169 # Disable autowrap. (When entering a the alternate screen, or anytime when 

170 # we have a prompt. - In the case of a REPL, like IPython, people can have 

171 # background threads, and it's hard for debugging if their output is not 

172 # wrapped.) 

173 if not previous_screen or not full_screen: 

174 output.disable_autowrap() 

175 

176 # When the previous screen has a different size, redraw everything anyway. 

177 # Also when we are done. (We might take up less rows, so clearing is important.) 

178 if ( 

179 is_done or not previous_screen or previous_width != width 

180 ): # XXX: also consider height?? 

181 current_pos = move_cursor(Point(x=0, y=0)) 

182 reset_attributes() 

183 output.erase_down() 

184 

185 previous_screen = Screen() 

186 

187 # Get height of the screen. 

188 # (height changes as we loop over data_buffer, so remember the current value.) 

189 # (Also make sure to clip the height to the size of the output.) 

190 current_height = min(screen.height, height) 

191 

192 # Loop over the rows. 

193 row_count = min(max(screen.height, previous_screen.height), height) 

194 

195 for y in range(row_count): 

196 new_row = screen.data_buffer[y] 

197 previous_row = previous_screen.data_buffer[y] 

198 zero_width_escapes_row = screen.zero_width_escapes[y] 

199 

200 new_max_line_len = min(width - 1, get_max_column_index(new_row)) 

201 previous_max_line_len = min(width - 1, get_max_column_index(previous_row)) 

202 

203 # Loop over the columns. 

204 c = 0 # Column counter. 

205 while c <= new_max_line_len: 

206 new_char = new_row[c] 

207 old_char = previous_row[c] 

208 char_width = new_char.width or 1 

209 

210 # When the old and new character at this position are different, 

211 # draw the output. (Because of the performance, we don't call 

212 # `Char.__ne__`, but inline the same expression.) 

213 if new_char.char != old_char.char or new_char.style != old_char.style: 

214 current_pos = move_cursor(Point(x=c, y=y)) 

215 

216 # Send injected escape sequences to output. 

217 if c in zero_width_escapes_row: 

218 write_raw(zero_width_escapes_row[c]) 

219 

220 output_char(new_char) 

221 current_pos = Point(x=current_pos.x + char_width, y=current_pos.y) 

222 

223 c += char_width 

224 

225 # If the new line is shorter, trim it. 

226 if previous_screen and new_max_line_len < previous_max_line_len: 

227 current_pos = move_cursor(Point(x=new_max_line_len + 1, y=y)) 

228 reset_attributes() 

229 output.erase_end_of_line() 

230 

231 # Correctly reserve vertical space as required by the layout. 

232 # When this is a new screen (drawn for the first time), or for some reason 

233 # higher than the previous one. Move the cursor once to the bottom of the 

234 # output. That way, we're sure that the terminal scrolls up, even when the 

235 # lower lines of the canvas just contain whitespace. 

236 

237 # The most obvious reason that we actually want this behavior is the avoid 

238 # the artifact of the input scrolling when the completion menu is shown. 

239 # (If the scrolling is actually wanted, the layout can still be build in a 

240 # way to behave that way by setting a dynamic height.) 

241 if current_height > previous_screen.height: 

242 current_pos = move_cursor(Point(x=0, y=current_height - 1)) 

243 

244 # Move cursor: 

245 if is_done: 

246 current_pos = move_cursor(Point(x=0, y=current_height)) 

247 output.erase_down() 

248 else: 

249 current_pos = move_cursor(screen.get_cursor_position(app.layout.current_window)) 

250 

251 if is_done or not full_screen: 

252 output.enable_autowrap() 

253 

254 # Always reset the color attributes. This is important because a background 

255 # thread could print data to stdout and we want that to be displayed in the 

256 # default colors. (Also, if a background color has been set, many terminals 

257 # give weird artifacts on resize events.) 

258 reset_attributes() 

259 

260 if screen.show_cursor: 

261 output.show_cursor() 

262 

263 return current_pos, last_style 

264 

265 

266class HeightIsUnknownError(Exception): 

267 "Information unavailable. Did not yet receive the CPR response." 

268 

269 

270class _StyleStringToAttrsCache(Dict[str, Attrs]): 

271 """ 

272 A cache structure that maps style strings to :class:`.Attr`. 

273 (This is an important speed up.) 

274 """ 

275 

276 def __init__( 

277 self, 

278 get_attrs_for_style_str: Callable[[str], Attrs], 

279 style_transformation: StyleTransformation, 

280 ) -> None: 

281 self.get_attrs_for_style_str = get_attrs_for_style_str 

282 self.style_transformation = style_transformation 

283 

284 def __missing__(self, style_str: str) -> Attrs: 

285 attrs = self.get_attrs_for_style_str(style_str) 

286 attrs = self.style_transformation.transform_attrs(attrs) 

287 

288 self[style_str] = attrs 

289 return attrs 

290 

291 

292class _StyleStringHasStyleCache(Dict[str, bool]): 

293 """ 

294 Cache for remember which style strings don't render the default output 

295 style (default fg/bg, no underline and no reverse and no blink). That way 

296 we know that we should render these cells, even when they're empty (when 

297 they contain a space). 

298 

299 Note: we don't consider bold/italic/hidden because they don't change the 

300 output if there's no text in the cell. 

301 """ 

302 

303 def __init__(self, style_string_to_attrs: dict[str, Attrs]) -> None: 

304 self.style_string_to_attrs = style_string_to_attrs 

305 

306 def __missing__(self, style_str: str) -> bool: 

307 attrs = self.style_string_to_attrs[style_str] 

308 is_default = bool( 

309 attrs.color 

310 or attrs.bgcolor 

311 or attrs.underline 

312 or attrs.strike 

313 or attrs.blink 

314 or attrs.reverse 

315 ) 

316 

317 self[style_str] = is_default 

318 return is_default 

319 

320 

321class CPR_Support(Enum): 

322 "Enum: whether or not CPR is supported." 

323 

324 SUPPORTED = "SUPPORTED" 

325 NOT_SUPPORTED = "NOT_SUPPORTED" 

326 UNKNOWN = "UNKNOWN" 

327 

328 

329class Renderer: 

330 """ 

331 Typical usage: 

332 

333 :: 

334 

335 output = Vt100_Output.from_pty(sys.stdout) 

336 r = Renderer(style, output) 

337 r.render(app, layout=...) 

338 """ 

339 

340 CPR_TIMEOUT = 2 # Time to wait until we consider CPR to be not supported. 

341 

342 def __init__( 

343 self, 

344 style: BaseStyle, 

345 output: Output, 

346 full_screen: bool = False, 

347 mouse_support: FilterOrBool = False, 

348 cpr_not_supported_callback: Callable[[], None] | None = None, 

349 ) -> None: 

350 self.style = style 

351 self.output = output 

352 self.full_screen = full_screen 

353 self.mouse_support = to_filter(mouse_support) 

354 self.cpr_not_supported_callback = cpr_not_supported_callback 

355 

356 # TODO: Move following state flags into `Vt100_Output`, similar to 

357 # `_cursor_shape_changed` and `_cursor_visible`. But then also 

358 # adjust the `Win32Output` to not call win32 APIs if nothing has 

359 # to be changed. 

360 

361 self._in_alternate_screen = False 

362 self._mouse_support_enabled = False 

363 self._bracketed_paste_enabled = False 

364 self._cursor_key_mode_reset = False 

365 

366 # Future set when we are waiting for a CPR flag. 

367 self._waiting_for_cpr_futures: deque[Future[None]] = deque() 

368 self.cpr_support = CPR_Support.UNKNOWN 

369 

370 if not output.responds_to_cpr: 

371 self.cpr_support = CPR_Support.NOT_SUPPORTED 

372 

373 # Cache for the style. 

374 self._attrs_for_style: _StyleStringToAttrsCache | None = None 

375 self._style_string_has_style: _StyleStringHasStyleCache | None = None 

376 self._last_style_hash: Hashable | None = None 

377 self._last_transformation_hash: Hashable | None = None 

378 self._last_color_depth: ColorDepth | None = None 

379 

380 self.reset(_scroll=True) 

381 

382 def reset(self, _scroll: bool = False, leave_alternate_screen: bool = True) -> None: 

383 # Reset position 

384 self._cursor_pos = Point(x=0, y=0) 

385 

386 # Remember the last screen instance between renderers. This way, 

387 # we can create a `diff` between two screens and only output the 

388 # difference. It's also to remember the last height. (To show for 

389 # instance a toolbar at the bottom position.) 

390 self._last_screen: Screen | None = None 

391 self._last_size: Size | None = None 

392 self._last_style: str | None = None 

393 self._last_cursor_shape: CursorShape | None = None 

394 

395 # Default MouseHandlers. (Just empty.) 

396 self.mouse_handlers = MouseHandlers() 

397 

398 #: Space from the top of the layout, until the bottom of the terminal. 

399 #: We don't know this until a `report_absolute_cursor_row` call. 

400 self._min_available_height = 0 

401 

402 # In case of Windows, also make sure to scroll to the current cursor 

403 # position. (Only when rendering the first time.) 

404 # It does nothing for vt100 terminals. 

405 if _scroll: 

406 self.output.scroll_buffer_to_prompt() 

407 

408 # Quit alternate screen. 

409 if self._in_alternate_screen and leave_alternate_screen: 

410 self.output.quit_alternate_screen() 

411 self._in_alternate_screen = False 

412 

413 # Disable mouse support. 

414 if self._mouse_support_enabled: 

415 self.output.disable_mouse_support() 

416 self._mouse_support_enabled = False 

417 

418 # Disable bracketed paste. 

419 if self._bracketed_paste_enabled: 

420 self.output.disable_bracketed_paste() 

421 self._bracketed_paste_enabled = False 

422 

423 self.output.reset_cursor_shape() 

424 self.output.show_cursor() 

425 

426 # NOTE: No need to set/reset cursor key mode here. 

427 

428 # Flush output. `disable_mouse_support` needs to write to stdout. 

429 self.output.flush() 

430 

431 @property 

432 def last_rendered_screen(self) -> Screen | None: 

433 """ 

434 The `Screen` class that was generated during the last rendering. 

435 This can be `None`. 

436 """ 

437 return self._last_screen 

438 

439 @property 

440 def height_is_known(self) -> bool: 

441 """ 

442 True when the height from the cursor until the bottom of the terminal 

443 is known. (It's often nicer to draw bottom toolbars only if the height 

444 is known, in order to avoid flickering when the CPR response arrives.) 

445 """ 

446 if self.full_screen or self._min_available_height > 0: 

447 return True 

448 try: 

449 self._min_available_height = self.output.get_rows_below_cursor_position() 

450 return True 

451 except NotImplementedError: 

452 return False 

453 

454 @property 

455 def rows_above_layout(self) -> int: 

456 """ 

457 Return the number of rows visible in the terminal above the layout. 

458 """ 

459 if self._in_alternate_screen: 

460 return 0 

461 elif self._min_available_height > 0: 

462 total_rows = self.output.get_size().rows 

463 last_screen_height = self._last_screen.height if self._last_screen else 0 

464 return total_rows - max(self._min_available_height, last_screen_height) 

465 else: 

466 raise HeightIsUnknownError("Rows above layout is unknown.") 

467 

468 def request_absolute_cursor_position(self) -> None: 

469 """ 

470 Get current cursor position. 

471 

472 We do this to calculate the minimum available height that we can 

473 consume for rendering the prompt. This is the available space below te 

474 cursor. 

475 

476 For vt100: Do CPR request. (answer will arrive later.) 

477 For win32: Do API call. (Answer comes immediately.) 

478 """ 

479 # Only do this request when the cursor is at the top row. (after a 

480 # clear or reset). We will rely on that in `report_absolute_cursor_row`. 

481 assert self._cursor_pos.y == 0 

482 

483 # In full-screen mode, always use the total height as min-available-height. 

484 if self.full_screen: 

485 self._min_available_height = self.output.get_size().rows 

486 return 

487 

488 # For Win32, we have an API call to get the number of rows below the 

489 # cursor. 

490 try: 

491 self._min_available_height = self.output.get_rows_below_cursor_position() 

492 return 

493 except NotImplementedError: 

494 pass 

495 

496 # Use CPR. 

497 if self.cpr_support == CPR_Support.NOT_SUPPORTED: 

498 return 

499 

500 def do_cpr() -> None: 

501 # Asks for a cursor position report (CPR). 

502 self._waiting_for_cpr_futures.append(Future()) 

503 self.output.ask_for_cpr() 

504 

505 if self.cpr_support == CPR_Support.SUPPORTED: 

506 do_cpr() 

507 return 

508 

509 # If we don't know whether CPR is supported, only do a request if 

510 # none is pending, and test it, using a timer. 

511 if self.waiting_for_cpr: 

512 return 

513 

514 do_cpr() 

515 

516 async def timer() -> None: 

517 await sleep(self.CPR_TIMEOUT) 

518 

519 # Not set in the meantime -> not supported. 

520 if self.cpr_support == CPR_Support.UNKNOWN: 

521 self.cpr_support = CPR_Support.NOT_SUPPORTED 

522 

523 if self.cpr_not_supported_callback: 

524 # Make sure to call this callback in the main thread. 

525 self.cpr_not_supported_callback() 

526 

527 get_app().create_background_task(timer()) 

528 

529 def report_absolute_cursor_row(self, row: int) -> None: 

530 """ 

531 To be called when we know the absolute cursor position. 

532 (As an answer of a "Cursor Position Request" response.) 

533 """ 

534 self.cpr_support = CPR_Support.SUPPORTED 

535 

536 # Calculate the amount of rows from the cursor position until the 

537 # bottom of the terminal. 

538 total_rows = self.output.get_size().rows 

539 rows_below_cursor = total_rows - row + 1 

540 

541 # Set the minimum available height. 

542 self._min_available_height = rows_below_cursor 

543 

544 # Pop and set waiting for CPR future. 

545 try: 

546 f = self._waiting_for_cpr_futures.popleft() 

547 except IndexError: 

548 pass # Received CPR response without having a CPR. 

549 else: 

550 f.set_result(None) 

551 

552 @property 

553 def waiting_for_cpr(self) -> bool: 

554 """ 

555 Waiting for CPR flag. True when we send the request, but didn't got a 

556 response. 

557 """ 

558 return bool(self._waiting_for_cpr_futures) 

559 

560 async def wait_for_cpr_responses(self, timeout: int = 1) -> None: 

561 """ 

562 Wait for a CPR response. 

563 """ 

564 cpr_futures = list(self._waiting_for_cpr_futures) # Make copy. 

565 

566 # When there are no CPRs in the queue. Don't do anything. 

567 if not cpr_futures or self.cpr_support == CPR_Support.NOT_SUPPORTED: 

568 return None 

569 

570 async def wait_for_responses() -> None: 

571 for response_f in cpr_futures: 

572 await response_f 

573 

574 async def wait_for_timeout() -> None: 

575 await sleep(timeout) 

576 

577 # Got timeout, erase queue. 

578 for response_f in cpr_futures: 

579 response_f.cancel() 

580 self._waiting_for_cpr_futures = deque() 

581 

582 tasks = { 

583 ensure_future(wait_for_responses()), 

584 ensure_future(wait_for_timeout()), 

585 } 

586 _, pending = await wait(tasks, return_when=FIRST_COMPLETED) 

587 for task in pending: 

588 task.cancel() 

589 

590 def render( 

591 self, app: Application[Any], layout: Layout, is_done: bool = False 

592 ) -> None: 

593 """ 

594 Render the current interface to the output. 

595 

596 :param is_done: When True, put the cursor at the end of the interface. We 

597 won't print any changes to this part. 

598 """ 

599 output = self.output 

600 

601 # Enter alternate screen. 

602 if self.full_screen and not self._in_alternate_screen: 

603 self._in_alternate_screen = True 

604 output.enter_alternate_screen() 

605 

606 # Enable bracketed paste. 

607 if not self._bracketed_paste_enabled: 

608 self.output.enable_bracketed_paste() 

609 self._bracketed_paste_enabled = True 

610 

611 # Reset cursor key mode. 

612 if not self._cursor_key_mode_reset: 

613 self.output.reset_cursor_key_mode() 

614 self._cursor_key_mode_reset = True 

615 

616 # Enable/disable mouse support. 

617 needs_mouse_support = self.mouse_support() 

618 

619 if needs_mouse_support and not self._mouse_support_enabled: 

620 output.enable_mouse_support() 

621 self._mouse_support_enabled = True 

622 

623 elif not needs_mouse_support and self._mouse_support_enabled: 

624 output.disable_mouse_support() 

625 self._mouse_support_enabled = False 

626 

627 # Create screen and write layout to it. 

628 size = output.get_size() 

629 screen = Screen() 

630 screen.show_cursor = False # Hide cursor by default, unless one of the 

631 # containers decides to display it. 

632 mouse_handlers = MouseHandlers() 

633 

634 # Calculate height. 

635 if self.full_screen: 

636 height = size.rows 

637 elif is_done: 

638 # When we are done, we don't necessary want to fill up until the bottom. 

639 height = layout.container.preferred_height( 

640 size.columns, size.rows 

641 ).preferred 

642 else: 

643 last_height = self._last_screen.height if self._last_screen else 0 

644 height = max( 

645 self._min_available_height, 

646 last_height, 

647 layout.container.preferred_height(size.columns, size.rows).preferred, 

648 ) 

649 

650 height = min(height, size.rows) 

651 

652 # When the size changes, don't consider the previous screen. 

653 if self._last_size != size: 

654 self._last_screen = None 

655 

656 # When we render using another style or another color depth, do a full 

657 # repaint. (Forget about the previous rendered screen.) 

658 # (But note that we still use _last_screen to calculate the height.) 

659 if ( 

660 self.style.invalidation_hash() != self._last_style_hash 

661 or app.style_transformation.invalidation_hash() 

662 != self._last_transformation_hash 

663 or app.color_depth != self._last_color_depth 

664 ): 

665 self._last_screen = None 

666 self._attrs_for_style = None 

667 self._style_string_has_style = None 

668 

669 if self._attrs_for_style is None: 

670 self._attrs_for_style = _StyleStringToAttrsCache( 

671 self.style.get_attrs_for_style_str, app.style_transformation 

672 ) 

673 if self._style_string_has_style is None: 

674 self._style_string_has_style = _StyleStringHasStyleCache( 

675 self._attrs_for_style 

676 ) 

677 

678 self._last_style_hash = self.style.invalidation_hash() 

679 self._last_transformation_hash = app.style_transformation.invalidation_hash() 

680 self._last_color_depth = app.color_depth 

681 

682 layout.container.write_to_screen( 

683 screen, 

684 mouse_handlers, 

685 WritePosition(xpos=0, ypos=0, width=size.columns, height=height), 

686 parent_style="", 

687 erase_bg=False, 

688 z_index=None, 

689 ) 

690 screen.draw_all_floats() 

691 

692 # When grayed. Replace all styles in the new screen. 

693 if app.exit_style: 

694 screen.append_style_to_content(app.exit_style) 

695 

696 # Process diff and write to output. 

697 self._cursor_pos, self._last_style = _output_screen_diff( 

698 app, 

699 output, 

700 screen, 

701 self._cursor_pos, 

702 app.color_depth, 

703 self._last_screen, 

704 self._last_style, 

705 is_done, 

706 full_screen=self.full_screen, 

707 attrs_for_style_string=self._attrs_for_style, 

708 style_string_has_style=self._style_string_has_style, 

709 size=size, 

710 previous_width=(self._last_size.columns if self._last_size else 0), 

711 ) 

712 self._last_screen = screen 

713 self._last_size = size 

714 self.mouse_handlers = mouse_handlers 

715 

716 # Handle cursor shapes. 

717 new_cursor_shape = app.cursor.get_cursor_shape(app) 

718 if ( 

719 self._last_cursor_shape is None 

720 or self._last_cursor_shape != new_cursor_shape 

721 ): 

722 output.set_cursor_shape(new_cursor_shape) 

723 self._last_cursor_shape = new_cursor_shape 

724 

725 # Flush buffered output. 

726 output.flush() 

727 

728 # Set visible windows in layout. 

729 app.layout.visible_windows = screen.visible_windows 

730 

731 if is_done: 

732 self.reset() 

733 

734 def erase(self, leave_alternate_screen: bool = True) -> None: 

735 """ 

736 Hide all output and put the cursor back at the first line. This is for 

737 instance used for running a system command (while hiding the CLI) and 

738 later resuming the same CLI.) 

739 

740 :param leave_alternate_screen: When True, and when inside an alternate 

741 screen buffer, quit the alternate screen. 

742 """ 

743 output = self.output 

744 

745 output.cursor_backward(self._cursor_pos.x) 

746 output.cursor_up(self._cursor_pos.y) 

747 output.erase_down() 

748 output.reset_attributes() 

749 output.enable_autowrap() 

750 

751 output.flush() 

752 

753 self.reset(leave_alternate_screen=leave_alternate_screen) 

754 

755 def clear(self) -> None: 

756 """ 

757 Clear screen and go to 0,0 

758 """ 

759 # Erase current output first. 

760 self.erase() 

761 

762 # Send "Erase Screen" command and go to (0, 0). 

763 output = self.output 

764 

765 output.erase_screen() 

766 output.cursor_goto(0, 0) 

767 output.flush() 

768 

769 self.request_absolute_cursor_position() 

770 

771 

772def print_formatted_text( 

773 output: Output, 

774 formatted_text: AnyFormattedText, 

775 style: BaseStyle, 

776 style_transformation: StyleTransformation | None = None, 

777 color_depth: ColorDepth | None = None, 

778) -> None: 

779 """ 

780 Print a list of (style_str, text) tuples in the given style to the output. 

781 """ 

782 fragments = to_formatted_text(formatted_text) 

783 style_transformation = style_transformation or DummyStyleTransformation() 

784 color_depth = color_depth or output.get_default_color_depth() 

785 

786 # Reset first. 

787 output.reset_attributes() 

788 output.enable_autowrap() 

789 last_attrs: Attrs | None = None 

790 

791 # Print all (style_str, text) tuples. 

792 attrs_for_style_string = _StyleStringToAttrsCache( 

793 style.get_attrs_for_style_str, style_transformation 

794 ) 

795 

796 for style_str, text, *_ in fragments: 

797 attrs = attrs_for_style_string[style_str] 

798 

799 # Set style attributes if something changed. 

800 if attrs != last_attrs: 

801 if attrs: 

802 output.set_attributes(attrs, color_depth) 

803 else: 

804 output.reset_attributes() 

805 last_attrs = attrs 

806 

807 # Print escape sequences as raw output 

808 if "[ZeroWidthEscape]" in style_str: 

809 output.write_raw(text) 

810 else: 

811 # Eliminate carriage returns 

812 text = text.replace("\r", "") 

813 # Insert a carriage return before every newline (important when the 

814 # front-end is a telnet client). 

815 text = text.replace("\n", "\r\n") 

816 output.write(text) 

817 

818 # Reset again. 

819 output.reset_attributes() 

820 output.flush()