Fix edge cases with very short windows
This commit is contained in:
51
babi.py
51
babi.py
@@ -85,21 +85,35 @@ def _write_header(
|
|||||||
stdscr.addstr(0, 0, s, curses.A_REVERSE)
|
stdscr.addstr(0, 0, s, curses.A_REVERSE)
|
||||||
|
|
||||||
|
|
||||||
def _write_status(stdscr: '_curses._CursesWindow', status: str) -> None:
|
|
||||||
stdscr.addstr(curses.LINES - 1, 0, ' ' * (curses.COLS - 1))
|
|
||||||
if status:
|
|
||||||
status = f' {status} '
|
|
||||||
offset = (curses.COLS - len(status)) // 2
|
|
||||||
stdscr.addstr(curses.LINES - 1, offset, status, curses.A_REVERSE)
|
|
||||||
|
|
||||||
|
|
||||||
def _write_lines(stdscr: '_curses._CursesWindow', lines: List[str]) -> None:
|
def _write_lines(stdscr: '_curses._CursesWindow', lines: List[str]) -> None:
|
||||||
lines_to_display = min(len(lines), curses.LINES - 2)
|
if curses.LINES == 1:
|
||||||
|
header, footer = 0, 0
|
||||||
|
elif curses.LINES == 2:
|
||||||
|
header, footer = 0, 1
|
||||||
|
else:
|
||||||
|
header, footer = 1, 1
|
||||||
|
|
||||||
|
max_lines = curses.LINES - header - footer
|
||||||
|
lines_to_display = min(len(lines), max_lines)
|
||||||
for i in range(lines_to_display):
|
for i in range(lines_to_display):
|
||||||
stdscr.addstr(i + 1, 0, lines[i][:curses.COLS])
|
line = lines[i][:curses.COLS].rstrip('\r\n').ljust(curses.COLS)
|
||||||
|
stdscr.insstr(i + header, 0, line)
|
||||||
blankline = ' ' * curses.COLS
|
blankline = ' ' * curses.COLS
|
||||||
for i in range(lines_to_display, curses.LINES - 2):
|
for i in range(lines_to_display, max_lines):
|
||||||
stdscr.addstr(i + 1, 0, blankline)
|
stdscr.insstr(i + header, 0, blankline)
|
||||||
|
|
||||||
|
|
||||||
|
def _write_status(stdscr: '_curses._CursesWindow', status: str) -> None:
|
||||||
|
if curses.LINES > 1 or status:
|
||||||
|
stdscr.insstr(curses.LINES - 1, 0, ' ' * curses.COLS)
|
||||||
|
if status:
|
||||||
|
status = f' {status} '
|
||||||
|
offset = (curses.COLS - len(status)) // 2
|
||||||
|
stdscr.addstr(curses.LINES - 1, offset, status, curses.A_REVERSE)
|
||||||
|
|
||||||
|
|
||||||
|
def _move(stdscr: '_curses._CursesWindow', x: int, y: int) -> None:
|
||||||
|
stdscr.move(y + (curses.LINES > 2), x)
|
||||||
|
|
||||||
|
|
||||||
def c_main(stdscr: '_curses._CursesWindow', args: argparse.Namespace) -> None:
|
def c_main(stdscr: '_curses._CursesWindow', args: argparse.Namespace) -> None:
|
||||||
@@ -122,17 +136,22 @@ def c_main(stdscr: '_curses._CursesWindow', args: argparse.Namespace) -> None:
|
|||||||
def _set_status(s: str) -> None:
|
def _set_status(s: str) -> None:
|
||||||
nonlocal status, status_action_counter
|
nonlocal status, status_action_counter
|
||||||
status = s
|
status = s
|
||||||
status_action_counter = 25
|
# if the window is only 1-tall, clear status quicker
|
||||||
|
if curses.LINES == 1:
|
||||||
|
status_action_counter = 1
|
||||||
|
else:
|
||||||
|
status_action_counter = 25
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if status_action_counter == 0:
|
if status_action_counter == 0:
|
||||||
status = ''
|
status = ''
|
||||||
status_action_counter -= 1
|
status_action_counter -= 1
|
||||||
|
|
||||||
_write_header(stdscr, filename, modified=False)
|
if curses.LINES > 2:
|
||||||
_write_status(stdscr, status)
|
_write_header(stdscr, filename, modified=False)
|
||||||
_write_lines(stdscr, lines)
|
_write_lines(stdscr, lines)
|
||||||
stdscr.move(position_y + 1, position_x)
|
_write_status(stdscr, status)
|
||||||
|
_move(stdscr, x=position_x, y=position_y)
|
||||||
|
|
||||||
wch = stdscr.get_wch()
|
wch = stdscr.get_wch()
|
||||||
key = wch if isinstance(wch, int) else ord(wch)
|
key = wch if isinstance(wch, int) else ord(wch)
|
||||||
|
|||||||
@@ -33,21 +33,20 @@ def run(*args, color=True, **kwargs):
|
|||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def and_exit(h):
|
def and_exit(h):
|
||||||
try:
|
yield
|
||||||
yield
|
# only try and exit in non-exceptional cases
|
||||||
finally:
|
h.press('C-x')
|
||||||
h.press('C-x')
|
h.await_exit()
|
||||||
h.await_exit()
|
|
||||||
|
|
||||||
|
|
||||||
def await_text_missing(h, text):
|
def await_text_missing(h, s):
|
||||||
"""largely based on await_text"""
|
"""largely based on await_text"""
|
||||||
for _ in h.poll_until_timeout():
|
for _ in h.poll_until_timeout():
|
||||||
screen = h.screenshot()
|
screen = h.screenshot()
|
||||||
munged = screen.replace('\n', '')
|
munged = screen.replace('\n', '')
|
||||||
if text not in munged: # pragma: no branch
|
if s not in munged: # pragma: no branch
|
||||||
return
|
return
|
||||||
raise AssertionError(f'Timeout while waiting for text {text!r} to appear')
|
raise AssertionError(f'Timeout while waiting for text {s!r} to disappear')
|
||||||
|
|
||||||
|
|
||||||
def get_size(h):
|
def get_size(h):
|
||||||
@@ -63,7 +62,7 @@ def resize(h, width, height):
|
|||||||
panes = 0
|
panes = 0
|
||||||
|
|
||||||
hsplit_w = current_w - width - 1
|
hsplit_w = current_w - width - 1
|
||||||
if hsplit_w > 0: # pragma: no branch # TODO
|
if hsplit_w > 0:
|
||||||
cmd = ('split-window', '-ht0', '-l', hsplit_w, 'sleep', 'infinity')
|
cmd = ('split-window', '-ht0', '-l', hsplit_w, 'sleep', 'infinity')
|
||||||
h.tmux.execute_command(*cmd)
|
h.tmux.execute_command(*cmd)
|
||||||
panes += 1
|
panes += 1
|
||||||
@@ -108,6 +107,43 @@ def test_window_bounds(tmpdir):
|
|||||||
h.press('DOWN')
|
h.press('DOWN')
|
||||||
|
|
||||||
|
|
||||||
|
def test_window_height_2(tmpdir):
|
||||||
|
# 2 tall:
|
||||||
|
# - header is hidden, otherwise behaviour is normal
|
||||||
|
f = tmpdir.join('f.txt')
|
||||||
|
f.write('hello world')
|
||||||
|
|
||||||
|
with run(str(f)) as h, and_exit(h):
|
||||||
|
h.await_text('hello world')
|
||||||
|
|
||||||
|
with resize(h, 80, 2):
|
||||||
|
await_text_missing(h, babi.VERSION_STR)
|
||||||
|
assert h.screenshot() == 'hello world\n\n'
|
||||||
|
h.press('C-j')
|
||||||
|
h.await_text('unknown key')
|
||||||
|
|
||||||
|
h.await_text(babi.VERSION_STR)
|
||||||
|
|
||||||
|
|
||||||
|
def test_window_height_1(tmpdir):
|
||||||
|
# 1 tall:
|
||||||
|
# - only file contents as body
|
||||||
|
# - status takes precedence over body, but cleared after single action
|
||||||
|
f = tmpdir.join('f.txt')
|
||||||
|
f.write('hello world')
|
||||||
|
|
||||||
|
with run(str(f)) as h, and_exit(h):
|
||||||
|
h.await_text('hello world')
|
||||||
|
|
||||||
|
with resize(h, 80, 1):
|
||||||
|
await_text_missing(h, babi.VERSION_STR)
|
||||||
|
assert h.screenshot() == 'hello world\n'
|
||||||
|
h.press('C-j')
|
||||||
|
h.await_text('unknown key')
|
||||||
|
h.press('Right')
|
||||||
|
await_text_missing(h, 'unknown key')
|
||||||
|
|
||||||
|
|
||||||
def test_status_clearing_behaviour():
|
def test_status_clearing_behaviour():
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('C-j')
|
h.press('C-j')
|
||||||
|
|||||||
Reference in New Issue
Block a user