Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/prompt_toolkit/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,16 @@ def get_max_column_index(row: dict[int, Char]) -> int:
new_max_line_len = min(width - 1, get_max_column_index(new_row))
previous_max_line_len = min(width - 1, get_max_column_index(previous_row))

# Whether this row has any real (non-blank) content at all. A row
# with nothing in it still gets a max index of 0 from
# `get_max_column_index`, same as a row whose only visible
# character happens to sit at column 0, so that can't be used here
# to tell the two apart.
new_row_has_content = any(
cell.char != " " or style_string_has_style[cell.style]
for cell in new_row.values()
)

# Loop over the columns.
c = 0 # Column counter.
while c <= new_max_line_len:
Expand All @@ -218,7 +228,12 @@ def get_max_column_index(row: dict[int, Char]) -> int:
if c in zero_width_escapes_row:
write_raw(zero_width_escapes_row[c])

output_char(new_char)
# Don't bother drawing a blank row just because its style
# differs from whatever was there before (this is what
# happens the first time an otherwise-empty line renders).
# There's nothing worth repainting.
if new_row_has_content:
output_char(new_char)
current_pos = Point(x=current_pos.x + char_width, y=current_pos.y)

c += char_width
Expand Down
98 changes: 98 additions & 0 deletions tests/test_renderer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""
Tests for `prompt_toolkit.renderer._output_screen_diff`.
"""

from __future__ import annotations

from prompt_toolkit.data_structures import Point, Size
from prompt_toolkit.layout.screen import Char, Screen
from prompt_toolkit.output import ColorDepth, DummyOutput
from prompt_toolkit.renderer import (
_output_screen_diff,
_StyleStringHasStyleCache,
_StyleStringToAttrsCache,
)
from prompt_toolkit.styles import DummyStyleTransformation, Style


class _CapturingOutput(DummyOutput):
"Like `DummyOutput`, but remembers everything written to it."

def __init__(self) -> None:
self.written: list[str] = []

def write(self, data: str) -> None:
self.written.append(data)


def _render(new_row_cells):
"""
Render a single ten-column, one-row screen against a blank previous
screen, given the (column, char, style) cells to put in the new row.
Returns the concatenation of everything that got written to the output.
"""
style = Style.from_dict({})
attrs_for_style_string = _StyleStringToAttrsCache(
style.get_attrs_for_style_str, DummyStyleTransformation()
)
style_string_has_style = _StyleStringHasStyleCache(attrs_for_style_string)

previous_screen = Screen()
previous_screen.width = 10
previous_screen.height = 1

screen = Screen()
screen.width = 10
screen.height = 1
for column, char, cell_style in new_row_cells:
screen.data_buffer[0][column] = Char(char, cell_style)

output = _CapturingOutput()

_output_screen_diff(
app=None,
output=output,
screen=screen,
current_pos=Point(x=0, y=0),
color_depth=ColorDepth.DEPTH_1_BIT,
previous_screen=previous_screen,
last_style=None,
is_done=True,
full_screen=False,
attrs_for_style_string=attrs_for_style_string,
style_string_has_style=style_string_has_style,
size=Size(rows=1, columns=10),
previous_width=10,
)
return "".join(output.written)


def test_blank_row_does_not_redraw_a_space():
"""
A line that never got any real content (just the default blank cell at
column 0) shouldn't have a space written for it, even on the first
render where its style differs from the previous screen's default.
"""
written = _render([(0, " ", "")])
assert " " not in written


def test_content_at_column_zero_is_still_drawn():
"""
A line whose only visible character happens to sit at column 0 must
still be drawn. This is the case a naive "skip column 0 when the row's
max index is 0" fix would break, since a lone character at column 0
also has a max index of 0.
"""
written = _render([(0, "$", "")])
assert "$" in written


def test_content_after_column_zero_is_still_drawn():
"""
A line with real content beyond column 0 is unaffected by the blank-row
check either way, but is worth pinning down alongside the two cases
above.
"""
written = _render([(0, " ", ""), (1, "$", "")])
assert "$" in written
Loading