0
0
mirror of https://github.com/ihabunek/toot.git synced 2025-06-30 22:18:36 -04:00

Rework paging to take terminal height into account

This commit is contained in:
Ivan Habunek 2025-01-21 11:04:54 +01:00
parent bfcf623448
commit ecc8033a41
No known key found for this signature in database
GPG Key ID: 01DB3DD0D824504C
2 changed files with 38 additions and 20 deletions

View File

@ -1,5 +1,6 @@
from functools import wraps from functools import wraps
from typing import Iterable, Optional, Tuple import shutil
from typing import Iterable, List, Optional, Tuple
from urllib.parse import quote from urllib.parse import quote
import click import click
@ -12,11 +13,12 @@ from toot.cli.validators import validate_instance, validate_positive
from toot.entities import ( from toot.entities import (
Account, Account,
Status, Status,
from_dict_list,
from_response, from_response,
from_response_list, from_response_list,
from_responses_batched, from_responses_batched,
) )
from toot.output import get_continue, print_timeline from toot.output import get_continue, get_max_width, get_terminal_height, get_terminal_width, print_timeline, status_lines
from toot.utils import drop_empty_values, str_bool_nullable from toot.utils import drop_empty_values, str_bool_nullable
@ -46,17 +48,14 @@ def common_timeline_options(func):
@click.option( @click.option(
"-p", "-p",
"--pager", "--pager",
help="Page the results, optionally define how many results to show per page", help="Page the results",
type=int, is_flag=True,
callback=validate_positive,
is_flag=False,
flag_value=10,
) )
@click.option( @click.option(
"-c", "-c",
"--clear", "--clear/--no-clear",
help="Clear the screen before printing. If paged, clear before each page.", help="Clear the screen before printing. If paged, clear before each page.",
is_flag=True, default=True,
) )
@wraps(func) @wraps(func)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
@ -92,7 +91,7 @@ def account(
max_id: Optional[str], max_id: Optional[str],
since_id: Optional[str], since_id: Optional[str],
limit: Optional[int], limit: Optional[int],
pager: Optional[int], pager: bool,
clear: bool, clear: bool,
json: bool, json: bool,
): ):
@ -120,7 +119,7 @@ def home(
max_id: Optional[str], max_id: Optional[str],
since_id: Optional[str], since_id: Optional[str],
limit: Optional[int], limit: Optional[int],
pager: Optional[int], pager: bool,
clear: bool, clear: bool,
json: bool, json: bool,
): ):
@ -148,7 +147,7 @@ def link(
max_id: Optional[str], max_id: Optional[str],
since_id: Optional[str], since_id: Optional[str],
limit: Optional[int], limit: Optional[int],
pager: Optional[int], pager: bool,
clear: bool, clear: bool,
json: bool, json: bool,
): ):
@ -170,19 +169,19 @@ def link(
_show_timeline(ctx, path, params, json, pager, clear, limit) _show_timeline(ctx, path, params, json, pager, clear, limit)
@timelines.command() @timelines.command("list")
@click.argument("list_name_or_id") @click.argument("list_name_or_id")
@common_timeline_options @common_timeline_options
@json_option @json_option
@pass_context @pass_context
def list( def list_cmd(
ctx: Context, ctx: Context,
list_name_or_id: str, list_name_or_id: str,
min_id: Optional[str], min_id: Optional[str],
max_id: Optional[str], max_id: Optional[str],
since_id: Optional[str], since_id: Optional[str],
limit: Optional[int], limit: Optional[int],
pager: Optional[int], pager: bool,
clear: bool, clear: bool,
json: bool, json: bool,
): ):
@ -230,7 +229,7 @@ def public(
max_id: Optional[str], max_id: Optional[str],
since_id: Optional[str], since_id: Optional[str],
limit: Optional[int], limit: Optional[int],
pager: Optional[int], pager: bool,
clear: bool, clear: bool,
local: Optional[bool], local: Optional[bool],
remote: Optional[bool], remote: Optional[bool],
@ -303,7 +302,7 @@ def tag(
max_id: Optional[str], max_id: Optional[str],
since_id: Optional[str], since_id: Optional[str],
limit: Optional[int], limit: Optional[int],
pager: Optional[int], pager: bool,
clear: bool, clear: bool,
local: Optional[bool], local: Optional[bool],
remote: Optional[bool], remote: Optional[bool],
@ -381,18 +380,33 @@ def _print_single(response: Response, clear: bool, limit: Optional[int]):
dim=True, dim=True,
) )
else: else:
click.echo("No statuses found containing the given tag") click.echo("No statuses found")
def _print_paged(responses: Iterable[Response], page_size: int, clear: bool): def _print_paged(responses: Iterable[Response], page_size: int, clear: bool):
width = get_max_width()
height = get_terminal_height()
separator = "" * width
def _page_generator():
batch_lines: List[str] = []
for response in responses:
statuses = from_dict_list(Status, response.json())
for status in statuses:
lines = [separator] + list(status_lines(status))
if len(batch_lines) + len(lines) > height - 2 and batch_lines:
yield "\n".join(batch_lines) + "\n" + separator
batch_lines = []
batch_lines.extend(lines)
first = True first = True
printed_any = False printed_any = False
for page in from_responses_batched(responses, Status, page_size): for page in _page_generator():
if not first and not get_continue(): if not first and not get_continue():
break break
if clear: if clear:
click.clear() click.clear()
print_timeline(page) click.echo(page)
first = False first = False
printed_any = True printed_any = True

View File

@ -21,6 +21,10 @@ def get_terminal_width() -> int:
return shutil.get_terminal_size().columns return shutil.get_terminal_size().columns
def get_terminal_height() -> int:
return shutil.get_terminal_size().lines
def get_width() -> int: def get_width() -> int:
return min(get_terminal_width(), get_max_width()) return min(get_terminal_width(), get_max_width())