#!/usr/bin/env python3 import curses import curses.textpad import locale import time locale.setlocale(locale.LC_ALL, '') logs = [] def main(stdscr): curses.curs_set(1) stdscr.nodelay(False) stdscr.keypad(True) prompt = "> " def resize_windows(): nonlocal main_win, input_win, maxy, maxx, input_box stdscr.clear() maxy, maxx = stdscr.getmaxyx() main_win = stdscr.derwin(maxy - 1, maxx, 0, 0) main_win.keypad(True) main_win.nodelay(True) main_win.scrollok(True) input_win = stdscr.derwin(1, maxx, maxy - 1, 0) input_win.keypad(True) input_box = input_win.derwin(1, maxx - len(prompt), 0, len(prompt)) draw_screen() def draw_screen(): if main_win is None or input_win is None: return main_win.erase() height, width = main_win.getmaxyx() start = max(0, len(logs) - height) visible = logs[start:] for i, line in enumerate(visible): main_win.addnstr(i, 0, line, width - 1) main_win.noutrefresh() input_win.erase() input_win.addstr(0, 0, prompt) if maxy > 1: stdscr.hline(maxy - 2, 0, curses.ACS_HLINE, maxx) input_win.noutrefresh() curses.doupdate() maxy = maxx = 0 main_win = input_win = input_box = None resize_windows() if input_box is None: return textbox = curses.textpad.Textbox(input_box, insert_mode=True) while True: draw_screen() def validator(ch): nonlocal logs if ch in (curses.KEY_RESIZE,): resize_windows() return 0 if ch in (10, 13): return 7 return ch input_box.erase() input_box.refresh() curses.curs_set(1) s = textbox.edit(validator).strip() curses.curs_set(0) if s: if s.strip().lower() in ("/quit", "/q", "quit"): break timestamp = time.strftime("%H:%M:%S") logs.append(f"[{timestamp}] {s}") else: pass if curses.is_term_resized(maxy, maxx): curses.resizeterm(*stdscr.getmaxyx()) resize_windows() if __name__ == "__main__": curses.wrapper(main)