commit c3b01d461b5d38996034be2218a6004b3fe2f82e
parent 1943a84c3b88c0b65124c828149f16018a6bb61e
Author: luke8086 <55237178+luke8086@users.noreply.github.com>
Date: Tue, 12 Jul 2022 20:22:47 +0000
Initial message index view with cursor navigation
Diffstat:
| M | retronews.py | | | 104 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- |
1 file changed, 99 insertions(+), 5 deletions(-)
diff --git a/retronews.py b/retronews.py
@@ -8,45 +8,139 @@
#
import curses
+import json
import sys
-from dataclasses import dataclass
+from dataclasses import dataclass, field
+from datetime import datetime
+from typing import List, Optional, TypedDict, TypeVar
+
+T = TypeVar("T")
+
+
+@dataclass
+class Message:
+ msg_id: str
+ author: str
+ title: str
+ date: datetime
+ index_position: int = 0
@dataclass
class AppState:
screen: "curses._CursesWindow"
+ index_window: "curses._CursesWindow"
+ messages: List[Message] = field(default_factory=list)
+ selected_message: Optional[Message] = None
+
+
+class HNSearchHit(TypedDict):
+ objectID: int
+ author: str
+ title: str
+ created_at_i: int
+
+
+def list_get(lst: List[T], index: int, default: Optional[T]) -> Optional[T]:
+ return lst[index] if 0 <= index < len(lst) else default
def cmd_quit(_: AppState):
sys.exit(0)
+def cmd_up(app: AppState) -> None:
+ pos = app.selected_message.index_position - 1 if app.selected_message else 0
+ app.selected_message = list_get(app.messages, pos, app.selected_message)
+
+
+def cmd_down(app: AppState) -> None:
+ pos = app.selected_message.index_position + 1 if app.selected_message else 0
+ app.selected_message = list_get(app.messages, pos, app.selected_message)
+
+
def cmd_unknown(_: AppState) -> None:
pass
KEY_BINDINGS = {
ord("q"): cmd_quit,
+ curses.KEY_UP: cmd_up,
+ curses.KEY_DOWN: cmd_down,
}
+def app_load_messages(app: AppState, messages: List[Message]) -> None:
+ for i, message in enumerate(messages):
+ message.index_position = i
+ message.title = f"{i} {message.title}"
+
+ app.messages = messages
+ app.selected_message = messages[0] if len(messages) > 0 else None
+
+
+def app_render_index_row(app: AppState, row: int, message: Message) -> None:
+ cursor = "->" if message == app.selected_message else " "
+ app.index_window.addstr(row, 0, f"{cursor} [{message.date}] [{message.author[:10]:10}] {message.title}")
+
+
+def app_render_index(app: AppState) -> None:
+ height = app.index_window.getmaxyx()[0]
+
+ offset = app.selected_message.index_position - height // 2 if app.selected_message else 0
+ offset = min(offset, len(app.messages) - height)
+ offset = max(offset, 0)
+
+ rows_to_render = min(height, len(app.messages) - offset)
+
+ for i in range(rows_to_render):
+ app_render_index_row(app, i, app.messages[i + offset])
+
+
+def app_render(app: AppState) -> None:
+ app.screen.erase()
+
+ app_render_index(app)
+
+ app.screen.refresh()
+
+
+def app_adjust_size(app: AppState) -> None:
+ app.index_window.mvwin(1, 0)
+ app.index_window.resize((curses.LINES - 2) // 3, curses.COLS)
+
+
def app_init(screen: "curses._CursesWindow") -> AppState:
- app = AppState(screen=screen)
curses.curs_set(0)
+
+ app = AppState(screen=screen, index_window=screen.subwin(1, 0))
+ app_adjust_size(app)
+ app_load_messages(app, hn_search_stories())
+
return app
-def app_render(st: AppState) -> None:
- st.screen.refresh()
+def hn_parse_search_hit(hit: HNSearchHit) -> Message:
+ return Message(
+ msg_id=f"{hit['objectID']}@hn",
+ author=hit["author"],
+ title=hit["title"],
+ date=datetime.fromtimestamp(hit["created_at_i"]),
+ )
+
+
+def hn_search_stories() -> List[Message]:
+ hits: List[HNSearchHit] = json.load(open("./tmp/index.json"))["hits"]
+ return [hn_parse_search_hit(hit) for hit in hits]
def main(screen: "curses._CursesWindow") -> None:
app = app_init(screen)
while True:
+ app_render(app)
c = app.screen.getch()
KEY_BINDINGS.get(c, cmd_unknown)(app)
- app_render(app)
if __name__ == "__main__":