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

344 statements  

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

1""" 

2Renders the command line on the console. 

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

4""" 

5from __future__ import annotations 

6 

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

8from collections import deque 

9from enum import Enum 

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

11 

12from prompt_toolkit.application.current import get_app 

13from prompt_toolkit.cursor_shapes import CursorShape 

14from prompt_toolkit.data_structures import Point, Size 

15from prompt_toolkit.filters import FilterOrBool, to_filter 

16from prompt_toolkit.formatted_text import AnyFormattedText, to_formatted_text 

17from prompt_toolkit.layout.mouse_handlers import MouseHandlers 

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

19from prompt_toolkit.output import ColorDepth, Output 

20from prompt_toolkit.styles import ( 

21 Attrs, 

22 BaseStyle, 

23 DummyStyleTransformation, 

24 StyleTransformation, 

25) 

26 

27if TYPE_CHECKING: 

28 from prompt_toolkit.application import Application 

29 from prompt_toolkit.layout.layout import Layout 

30 

31 

32__all__ = [ 

33 "Renderer", 

34 "print_formatted_text", 

35] 

36 

37 

38def _output_screen_diff( 

39 app: Application[Any], 

40 output: Output, 

41 screen: Screen, 

42 current_pos: Point, 

43 color_depth: ColorDepth, 

44 previous_screen: Screen | None, 

45 last_style: str | None, 

46 is_done: bool, # XXX: drop is_done 

47 full_screen: bool, 

48 attrs_for_style_string: _StyleStringToAttrsCache, 

49 style_string_has_style: _StyleStringHasStyleCache, 

50 size: Size, 

51 previous_width: int, 

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

53 """ 

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

55 

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

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

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

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

60 object that only paint the changes to the terminal. 

61 

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

63 Don't change things without profiling first. 

64 

65 :param current_pos: Current cursor position. 

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

67 character. (Color/attributes.) 

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

69 :param width: The width of the terminal. 

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

71 """ 

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

73 

74 #: Variable for capturing the output. 

75 write = output.write 

76 write_raw = output.write_raw 

77 

78 # Create locals for the most used output methods. 

79 # (Save expensive attribute lookups.) 

80 _output_set_attributes = output.set_attributes 

81 _output_reset_attributes = output.reset_attributes 

82 _output_cursor_forward = output.cursor_forward 

83 _output_cursor_up = output.cursor_up 

84 _output_cursor_backward = output.cursor_backward 

85 

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

87 output.hide_cursor() 

88 

89 def reset_attributes() -> None: 

90 "Wrapper around Output.reset_attributes." 

91 nonlocal last_style 

92 _output_reset_attributes() 

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

94 

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

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

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

98 

99 if new.y > current_y: 

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

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

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

103 # background color. 

104 reset_attributes() 

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

106 current_x = 0 

107 _output_cursor_forward(new.x) 

108 return new 

109 elif new.y < current_y: 

110 _output_cursor_up(current_y - new.y) 

111 

112 if current_x >= width - 1: 

113 write("\r") 

114 _output_cursor_forward(new.x) 

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

116 _output_cursor_backward(current_x - new.x) 

117 elif new.x > current_x: 

118 _output_cursor_forward(new.x - current_x) 

119 

120 return new 

121 

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

123 """ 

124 Write the output of this character. 

125 """ 

126 nonlocal last_style 

127 

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

129 # style again. 

130 if last_style == char.style: 

131 write(char.char) 

132 else: 

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

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

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

136 # be applied, because of style transformations. 

137 new_attrs = attrs_for_style_string[char.style] 

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

139 _output_set_attributes(new_attrs, color_depth) 

140 

141 write(char.char) 

142 last_style = char.style 

143 

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

145 """ 

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

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

148 terminal output. 

149 

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

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

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

153 the cursor position moves around. 

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

155 (cursor-line). 

156 """ 

157 numbers = ( 

158 index 

159 for index, cell in row.items() 

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

161 ) 

162 return max(numbers, default=0) 

163 

164 # Render for the first time: reset styling. 

165 if not previous_screen: 

166 reset_attributes() 

167 

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

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

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

171 # wrapped.) 

172 if not previous_screen or not full_screen: 

173 output.disable_autowrap() 

174 

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

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

177 if ( 

178 is_done or not previous_screen or previous_width != width 

179 ): # XXX: also consider height?? 

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

181 reset_attributes() 

182 output.erase_down() 

183 

184 previous_screen = Screen() 

185 

186 # Get height of the screen. 

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

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

189 current_height = min(screen.height, height) 

190 

191 # Loop over the rows. 

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

193 

194 for y in range(row_count): 

195 new_row = screen.data_buffer[y] 

196 previous_row = previous_screen.data_buffer[y] 

197 zero_width_escapes_row = screen.zero_width_escapes[y] 

198 

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

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

201 

202 # Loop over the columns. 

203 c = 0 # Column counter. 

204 while c <= new_max_line_len: 

205 new_char = new_row[c] 

206 old_char = previous_row[c] 

207 char_width = new_char.width or 1 

208 

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

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

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

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

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

214 

215 # Send injected escape sequences to output. 

216 if c in zero_width_escapes_row: 

217 write_raw(zero_width_escapes_row[c]) 

218 

219 output_char(new_char) 

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

221 

222 c += char_width 

223 

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

225 if previous_screen and new_max_line_len < previous_max_line_len: 

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

227 reset_attributes() 

228 output.erase_end_of_line() 

229 

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

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

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

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

234 # lower lines of the canvas just contain whitespace. 

235 

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

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

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

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

240 if current_height > previous_screen.height: 

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

242 

243 # Move cursor: 

244 if is_done: 

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

246 output.erase_down() 

247 else: 

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

249 

250 if is_done or not full_screen: 

251 output.enable_autowrap() 

252 

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

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

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

256 # give weird artifacts on resize events.) 

257 reset_attributes() 

258 

259 if screen.show_cursor or is_done: 

260 output.show_cursor() 

261 

262 return current_pos, last_style 

263 

264 

265class HeightIsUnknownError(Exception): 

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

267 

268 

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

270 """ 

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

272 (This is an important speed up.) 

273 """ 

274 

275 def __init__( 

276 self, 

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

278 style_transformation: StyleTransformation, 

279 ) -> None: 

280 self.get_attrs_for_style_str = get_attrs_for_style_str 

281 self.style_transformation = style_transformation 

282 

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

284 attrs = self.get_attrs_for_style_str(style_str) 

285 attrs = self.style_transformation.transform_attrs(attrs) 

286 

287 self[style_str] = attrs 

288 return attrs 

289 

290 

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

292 """ 

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

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

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

296 they contain a space). 

297 

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

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

300 """ 

301 

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

303 self.style_string_to_attrs = style_string_to_attrs 

304 

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

306 attrs = self.style_string_to_attrs[style_str] 

307 is_default = bool( 

308 attrs.color 

309 or attrs.bgcolor 

310 or attrs.underline 

311 or attrs.strike 

312 or attrs.blink 

313 or attrs.reverse 

314 ) 

315 

316 self[style_str] = is_default 

317 return is_default 

318 

319 

320class CPR_Support(Enum): 

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

322 

323 SUPPORTED = "SUPPORTED" 

324 NOT_SUPPORTED = "NOT_SUPPORTED" 

325 UNKNOWN = "UNKNOWN" 

326 

327 

328class Renderer: 

329 """ 

330 Typical usage: 

331 

332 :: 

333 

334 output = Vt100_Output.from_pty(sys.stdout) 

335 r = Renderer(style, output) 

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

337 """ 

338 

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

340 

341 def __init__( 

342 self, 

343 style: BaseStyle, 

344 output: Output, 

345 full_screen: bool = False, 

346 mouse_support: FilterOrBool = False, 

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

348 ) -> None: 

349 self.style = style 

350 self.output = output 

351 self.full_screen = full_screen 

352 self.mouse_support = to_filter(mouse_support) 

353 self.cpr_not_supported_callback = cpr_not_supported_callback 

354 

355 self._in_alternate_screen = False 

356 self._mouse_support_enabled = False 

357 self._bracketed_paste_enabled = False 

358 self._cursor_key_mode_reset = False 

359 

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

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

362 self.cpr_support = CPR_Support.UNKNOWN 

363 

364 if not output.responds_to_cpr: 

365 self.cpr_support = CPR_Support.NOT_SUPPORTED 

366 

367 # Cache for the style. 

368 self._attrs_for_style: _StyleStringToAttrsCache | None = None 

369 self._style_string_has_style: _StyleStringHasStyleCache | None = None 

370 self._last_style_hash: Hashable | None = None 

371 self._last_transformation_hash: Hashable | None = None 

372 self._last_color_depth: ColorDepth | None = None 

373 

374 self.reset(_scroll=True) 

375 

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

377 # Reset position 

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

379 

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

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

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

383 # instance a toolbar at the bottom position.) 

384 self._last_screen: Screen | None = None 

385 self._last_size: Size | None = None 

386 self._last_style: str | None = None 

387 self._last_cursor_shape: CursorShape | None = None 

388 

389 # Default MouseHandlers. (Just empty.) 

390 self.mouse_handlers = MouseHandlers() 

391 

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

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

394 self._min_available_height = 0 

395 

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

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

398 # It does nothing for vt100 terminals. 

399 if _scroll: 

400 self.output.scroll_buffer_to_prompt() 

401 

402 # Quit alternate screen. 

403 if self._in_alternate_screen and leave_alternate_screen: 

404 self.output.quit_alternate_screen() 

405 self._in_alternate_screen = False 

406 

407 # Disable mouse support. 

408 if self._mouse_support_enabled: 

409 self.output.disable_mouse_support() 

410 self._mouse_support_enabled = False 

411 

412 # Disable bracketed paste. 

413 if self._bracketed_paste_enabled: 

414 self.output.disable_bracketed_paste() 

415 self._bracketed_paste_enabled = False 

416 

417 self.output.reset_cursor_shape() 

418 

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

420 

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

422 self.output.flush() 

423 

424 @property 

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

426 """ 

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

428 This can be `None`. 

429 """ 

430 return self._last_screen 

431 

432 @property 

433 def height_is_known(self) -> bool: 

434 """ 

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

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

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

438 """ 

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

440 return True 

441 try: 

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

443 return True 

444 except NotImplementedError: 

445 return False 

446 

447 @property 

448 def rows_above_layout(self) -> int: 

449 """ 

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

451 """ 

452 if self._in_alternate_screen: 

453 return 0 

454 elif self._min_available_height > 0: 

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

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

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

458 else: 

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

460 

461 def request_absolute_cursor_position(self) -> None: 

462 """ 

463 Get current cursor position. 

464 

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

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

467 cursor. 

468 

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

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

471 """ 

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

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

474 assert self._cursor_pos.y == 0 

475 

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

477 if self.full_screen: 

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

479 return 

480 

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

482 # cursor. 

483 try: 

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

485 return 

486 except NotImplementedError: 

487 pass 

488 

489 # Use CPR. 

490 if self.cpr_support == CPR_Support.NOT_SUPPORTED: 

491 return 

492 

493 def do_cpr() -> None: 

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

495 self._waiting_for_cpr_futures.append(Future()) 

496 self.output.ask_for_cpr() 

497 

498 if self.cpr_support == CPR_Support.SUPPORTED: 

499 do_cpr() 

500 return 

501 

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

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

504 if self.waiting_for_cpr: 

505 return 

506 

507 do_cpr() 

508 

509 async def timer() -> None: 

510 await sleep(self.CPR_TIMEOUT) 

511 

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

513 if self.cpr_support == CPR_Support.UNKNOWN: 

514 self.cpr_support = CPR_Support.NOT_SUPPORTED 

515 

516 if self.cpr_not_supported_callback: 

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

518 self.cpr_not_supported_callback() 

519 

520 get_app().create_background_task(timer()) 

521 

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

523 """ 

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

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

526 """ 

527 self.cpr_support = CPR_Support.SUPPORTED 

528 

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

530 # bottom of the terminal. 

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

532 rows_below_cursor = total_rows - row + 1 

533 

534 # Set the minimum available height. 

535 self._min_available_height = rows_below_cursor 

536 

537 # Pop and set waiting for CPR future. 

538 try: 

539 f = self._waiting_for_cpr_futures.popleft() 

540 except IndexError: 

541 pass # Received CPR response without having a CPR. 

542 else: 

543 f.set_result(None) 

544 

545 @property 

546 def waiting_for_cpr(self) -> bool: 

547 """ 

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

549 response. 

550 """ 

551 return bool(self._waiting_for_cpr_futures) 

552 

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

554 """ 

555 Wait for a CPR response. 

556 """ 

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

558 

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

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

561 return None 

562 

563 async def wait_for_responses() -> None: 

564 for response_f in cpr_futures: 

565 await response_f 

566 

567 async def wait_for_timeout() -> None: 

568 await sleep(timeout) 

569 

570 # Got timeout, erase queue. 

571 for response_f in cpr_futures: 

572 response_f.cancel() 

573 self._waiting_for_cpr_futures = deque() 

574 

575 tasks = { 

576 ensure_future(wait_for_responses()), 

577 ensure_future(wait_for_timeout()), 

578 } 

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

580 for task in pending: 

581 task.cancel() 

582 

583 def render( 

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

585 ) -> None: 

586 """ 

587 Render the current interface to the output. 

588 

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

590 won't print any changes to this part. 

591 """ 

592 output = self.output 

593 

594 # Enter alternate screen. 

595 if self.full_screen and not self._in_alternate_screen: 

596 self._in_alternate_screen = True 

597 output.enter_alternate_screen() 

598 

599 # Enable bracketed paste. 

600 if not self._bracketed_paste_enabled: 

601 self.output.enable_bracketed_paste() 

602 self._bracketed_paste_enabled = True 

603 

604 # Reset cursor key mode. 

605 if not self._cursor_key_mode_reset: 

606 self.output.reset_cursor_key_mode() 

607 self._cursor_key_mode_reset = True 

608 

609 # Enable/disable mouse support. 

610 needs_mouse_support = self.mouse_support() 

611 

612 if needs_mouse_support and not self._mouse_support_enabled: 

613 output.enable_mouse_support() 

614 self._mouse_support_enabled = True 

615 

616 elif not needs_mouse_support and self._mouse_support_enabled: 

617 output.disable_mouse_support() 

618 self._mouse_support_enabled = False 

619 

620 # Create screen and write layout to it. 

621 size = output.get_size() 

622 screen = Screen() 

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

624 # containers decides to display it. 

625 mouse_handlers = MouseHandlers() 

626 

627 # Calculate height. 

628 if self.full_screen: 

629 height = size.rows 

630 elif is_done: 

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

632 height = layout.container.preferred_height( 

633 size.columns, size.rows 

634 ).preferred 

635 else: 

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

637 height = max( 

638 self._min_available_height, 

639 last_height, 

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

641 ) 

642 

643 height = min(height, size.rows) 

644 

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

646 if self._last_size != size: 

647 self._last_screen = None 

648 

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

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

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

652 if ( 

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

654 or app.style_transformation.invalidation_hash() 

655 != self._last_transformation_hash 

656 or app.color_depth != self._last_color_depth 

657 ): 

658 self._last_screen = None 

659 self._attrs_for_style = None 

660 self._style_string_has_style = None 

661 

662 if self._attrs_for_style is None: 

663 self._attrs_for_style = _StyleStringToAttrsCache( 

664 self.style.get_attrs_for_style_str, app.style_transformation 

665 ) 

666 if self._style_string_has_style is None: 

667 self._style_string_has_style = _StyleStringHasStyleCache( 

668 self._attrs_for_style 

669 ) 

670 

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

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

673 self._last_color_depth = app.color_depth 

674 

675 layout.container.write_to_screen( 

676 screen, 

677 mouse_handlers, 

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

679 parent_style="", 

680 erase_bg=False, 

681 z_index=None, 

682 ) 

683 screen.draw_all_floats() 

684 

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

686 if app.exit_style: 

687 screen.append_style_to_content(app.exit_style) 

688 

689 # Process diff and write to output. 

690 self._cursor_pos, self._last_style = _output_screen_diff( 

691 app, 

692 output, 

693 screen, 

694 self._cursor_pos, 

695 app.color_depth, 

696 self._last_screen, 

697 self._last_style, 

698 is_done, 

699 full_screen=self.full_screen, 

700 attrs_for_style_string=self._attrs_for_style, 

701 style_string_has_style=self._style_string_has_style, 

702 size=size, 

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

704 ) 

705 self._last_screen = screen 

706 self._last_size = size 

707 self.mouse_handlers = mouse_handlers 

708 

709 # Handle cursor shapes. 

710 new_cursor_shape = app.cursor.get_cursor_shape(app) 

711 if ( 

712 self._last_cursor_shape is None 

713 or self._last_cursor_shape != new_cursor_shape 

714 ): 

715 output.set_cursor_shape(new_cursor_shape) 

716 self._last_cursor_shape = new_cursor_shape 

717 

718 # Flush buffered output. 

719 output.flush() 

720 

721 # Set visible windows in layout. 

722 app.layout.visible_windows = screen.visible_windows 

723 

724 if is_done: 

725 self.reset() 

726 

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

728 """ 

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

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

731 later resuming the same CLI.) 

732 

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

734 screen buffer, quit the alternate screen. 

735 """ 

736 output = self.output 

737 

738 output.cursor_backward(self._cursor_pos.x) 

739 output.cursor_up(self._cursor_pos.y) 

740 output.erase_down() 

741 output.reset_attributes() 

742 output.enable_autowrap() 

743 

744 output.flush() 

745 

746 self.reset(leave_alternate_screen=leave_alternate_screen) 

747 

748 def clear(self) -> None: 

749 """ 

750 Clear screen and go to 0,0 

751 """ 

752 # Erase current output first. 

753 self.erase() 

754 

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

756 output = self.output 

757 

758 output.erase_screen() 

759 output.cursor_goto(0, 0) 

760 output.flush() 

761 

762 self.request_absolute_cursor_position() 

763 

764 

765def print_formatted_text( 

766 output: Output, 

767 formatted_text: AnyFormattedText, 

768 style: BaseStyle, 

769 style_transformation: StyleTransformation | None = None, 

770 color_depth: ColorDepth | None = None, 

771) -> None: 

772 """ 

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

774 """ 

775 fragments = to_formatted_text(formatted_text) 

776 style_transformation = style_transformation or DummyStyleTransformation() 

777 color_depth = color_depth or output.get_default_color_depth() 

778 

779 # Reset first. 

780 output.reset_attributes() 

781 output.enable_autowrap() 

782 last_attrs: Attrs | None = None 

783 

784 # Print all (style_str, text) tuples. 

785 attrs_for_style_string = _StyleStringToAttrsCache( 

786 style.get_attrs_for_style_str, style_transformation 

787 ) 

788 

789 for style_str, text, *_ in fragments: 

790 attrs = attrs_for_style_string[style_str] 

791 

792 # Set style attributes if something changed. 

793 if attrs != last_attrs: 

794 if attrs: 

795 output.set_attributes(attrs, color_depth) 

796 else: 

797 output.reset_attributes() 

798 last_attrs = attrs 

799 

800 # Print escape sequences as raw output 

801 if "[ZeroWidthEscape]" in style_str: 

802 output.write_raw(text) 

803 else: 

804 # Eliminate carriage returns 

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

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

807 # front-end is a telnet client). 

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

809 output.write(text) 

810 

811 # Reset again. 

812 output.reset_attributes() 

813 output.flush()