The simplest "select a row from a table" implementation I could come up with #1785
Replies: 5 comments
-
Here's an improved version using a live table: from datetime import datetime
import os
from rich import box
from rich.live import Live
from rich.table import Table
from rich.style import Style
from getchar import getchar
SELECTED = Style(color="blue", bgcolor="white", bold=True)
UP = "\x1b[A"
DOWN = "\x1b[B"
ENTER = "\r"
def generate_table(headers, rows, selected) -> Table:
table = Table(box=box.MINIMAL)
for h in headers:
table.add_column(h)
for i, row in enumerate(rows):
table.add_row(*row, style=SELECTED if i == selected else None)
return table
def format_ts(ts):
return datetime.utcfromtimestamp(ts).strftime("%Y-%m-%d")
# get the first 10 items from os.listdir to show in the table
headers = ["filename", "size (bytes)", "modified"]
items = [
(fname, str(stat.st_size), format_ts(stat.st_mtime))
for fname, stat in [(f, os.stat(f)) for f in os.listdir()[:5]]
][:10]
selected = 0
with Live(generate_table(headers, items, selected), auto_refresh=False) as live:
while True:
ch = getchar(False)
if ch == UP:
selected = max(0, selected - 1)
elif ch == DOWN:
selected = min(len(items) - 1, selected + 1)
elif ch == ENTER:
live.stop()
print("you selected: ", items[selected])
break
live.update(generate_table(headers, items, selected), refresh=True) One thing to note is that it doesn't handle the table being larger than the available vertical screen space. |
Beta Was this translation helpful? Give feedback.
-
And, finally a version that uses readchar ( from datetime import datetime
import os
from rich.console import Console
from rich.live import Live
from rich.table import Table
from rich.style import Style
from rich import box
# pip install readchar
# https://github.com/magmax/python-readchar seems low-overhead and simple
from readchar import readkey, key
SELECTED = Style(color="blue", bgcolor="white", bold=True)
def generate_table(console, headers, rows, selected) -> Table:
table = Table(box=box.MINIMAL)
for h in headers:
table.add_column(h)
size = console.height - 4
if len(rows) + 3 > size:
if selected < size / 2:
rows = rows[:size]
elif selected + size / 2 > len(items):
rows = rows[-size:]
selected -= len(items) - size
else:
rows = rows[selected - size // 2 : selected + size // 2]
selected -= selected - size // 2
for i, row in enumerate(rows):
table.add_row(*row, style=SELECTED if i == selected else None)
return table
def format_ts(ts):
return datetime.utcfromtimestamp(ts).strftime("%Y-%m-%d")
headers = ["filename", "size (bytes)", "modified"]
items = [
(fname, str(stat.st_size), format_ts(stat.st_mtime))
for fname, stat in [(f, os.stat(f)) for f in os.listdir()]
]
console = Console()
selected = 0
with Live(
generate_table(console, headers, items, selected), auto_refresh=False
) as live:
while True:
ch = readkey()
if ch == key.UP or ch == "k":
selected = max(0, selected - 1)
if ch == key.DOWN or ch == "j":
selected = min(len(items) - 1, selected + 1)
if ch == key.ENTER:
live.stop()
print("you selected: ", items[selected])
break
live.update(generate_table(console, headers, items, selected), refresh=True) |
Beta Was this translation helpful? Give feedback.
-
Learning about the Rich library and wanted to make a scrollable table for a task tracker type object. This was very helpful. |
Beta Was this translation helpful? Give feedback.
-
Excellent! |
Beta Was this translation helpful? Give feedback.
-
Updated for newer python, and to gracefully handle ctrl-c from datetime import datetime, UTC
import os
import sys
from os import path
import signal
from rich.console import Console
from rich.live import Live
from rich.table import Table
from rich.style import Style
from rich import box
# pip install readchar
# https://github.com/magmax/python-readchar seems low-overhead and simple
from readchar import readkey, key
SELECTED = Style(color="blue", bgcolor="white", bold=True)
# if ctrl-c, quit gracefully
signal.signal(signal.SIGINT, lambda _, __: sys.exit(0))
def generate_table(console, headers, rows, selected) -> Table:
table = Table(box=box.MINIMAL)
for h in headers:
table.add_column(h)
size = console.height - 4
if len(rows) + 3 > size:
if selected < size / 2:
rows = rows[:size]
elif selected + size / 2 > len(items):
rows = rows[-size:]
selected -= len(items) - size
else:
rows = rows[selected - size // 2 : selected + size // 2]
selected -= selected - size // 2
for i, row in enumerate(rows):
table.add_row(*row, style=SELECTED if i == selected else None)
return table
def format_ts(ts):
return datetime.fromtimestamp(ts, UTC).strftime("%Y-%m-%d")
headers = ["filename", "size (bytes)", "modified"]
dir = sys.argv[1] if len(sys.argv) > 1 else "."
items = sorted(
(fname, str(stat.st_size), format_ts(stat.st_mtime))
for fname, stat in [(f, os.stat(path.join(dir, f))) for f in os.listdir(dir)]
)
console = Console()
selected = 0
with Live(
generate_table(console, headers, items, selected), auto_refresh=False
) as live:
while ch := readkey():
if ch == key.UP or ch == "k":
selected = max(0, selected - 1)
if ch == key.DOWN or ch == "j":
selected = min(len(items) - 1, selected + 1)
if ch == key.ENTER:
live.stop()
print("you selected: ", items[selected])
break
live.update(generate_table(console, headers, items, selected), refresh=True) |
Beta Was this translation helpful? Give feedback.
-
I wanted to allow a user to pick an option from a list using rich, and not needing support for the windows command line.
Textual/click/etc seemed pretty heavy, and I wanted to know how this would work, so I wanted to not add any dependencies.
Here's the simplest implementation I could come up with; I welcome feedback on how to make it simpler or better, but I'm putting it here mainly for anybody else to see how they might begin:
Screen.Recording.2021-12-28.at.12.48.24.PM.mov
(update: see comment below for a much improved version)
menutest.py:
getchar.py is a modification of click's input handling code:
Beta Was this translation helpful? Give feedback.
All reactions