Files
babi/babi.py
2019-07-27 19:22:43 -07:00

147 lines
3.9 KiB
Python

import _curses
import argparse
import curses
from typing import Dict
from typing import Tuple
VERSION_STR = 'babi v0'
def _get_color_pair_mapping() -> Dict[Tuple[int, int], int]:
ret = {}
i = 0
for bg in range(-1, 16):
for fg in range(bg, 16):
ret[(fg, bg)] = i
i += 1
return ret
COLORS = _get_color_pair_mapping()
del _get_color_pair_mapping
def _has_colors() -> bool:
# https://github.com/python/typeshed/pull/3115
return curses.has_colors and curses.COLORS >= 16 # type: ignore
def _color(fg: int, bg: int) -> int:
if _has_colors():
if bg > fg:
return curses.A_REVERSE | curses.color_pair(COLORS[(bg, fg)])
else:
return curses.color_pair(COLORS[(fg, bg)])
else:
if bg > fg:
return curses.A_REVERSE | curses.color_pair(0)
else:
return curses.color_pair(0)
def _init_colors(stdscr: '_curses._CursesWindow') -> None:
curses.use_default_colors()
if not _has_colors():
return
for (fg, bg), pair in COLORS.items():
if pair == 0: # cannot reset pair 0
continue
curses.init_pair(pair, fg, bg)
def _color_test(stdscr: '_curses._CursesWindow') -> None:
maxy, maxx = stdscr.getmaxyx()
if maxy < 16 or maxx < 64:
raise SystemExit('--color-test needs a window of at least 64 x 16')
x = y = 0
for fg in range(-1, 16):
for bg in range(-1, 16):
if bg > fg:
s = f'*{COLORS[bg, fg]:3}'
else:
s = f' {COLORS[fg, bg]:3}'
stdscr.addstr(y, x, s, _color(fg, bg))
x += 4
y += 1
x = 0
stdscr.get_wch()
def _write_header(
stdscr: '_curses._CursesWindow',
filename: str,
*,
modified: bool,
) -> None:
filename = filename or '<<new file>>'
if modified:
filename += ' *'
centered_filename = filename.center(curses.COLS)[len(VERSION_STR) + 2:]
s = f' {VERSION_STR} {centered_filename}'
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 c_main(stdscr: '_curses._CursesWindow', args: argparse.Namespace) -> None:
_init_colors(stdscr)
if args.color_test:
return _color_test(stdscr)
filename = args.filename
status = ''
status_action_counter = -1
position_y, position_x = 0, 0
def _set_status(s: str) -> None:
nonlocal status, status_action_counter
status = s
status_action_counter = 25
while True:
if status_action_counter == 0:
status = ''
status_action_counter -= 1
_write_header(stdscr, filename, modified=False)
_write_status(stdscr, status)
stdscr.move(position_y + 1, position_x)
wch = stdscr.get_wch()
key = wch if isinstance(wch, int) else ord(wch)
keyname = curses.keyname(key)
if key == curses.KEY_DOWN:
position_y = min(position_y + 1, curses.LINES - 2)
elif key == curses.KEY_UP:
position_y = max(position_y - 1, 0)
elif key == curses.KEY_RIGHT:
position_x = min(position_x + 1, curses.COLS - 1)
elif key == curses.KEY_LEFT:
position_x = max(position_x - 1, 0)
elif keyname == b'^X':
return
else:
_set_status(f'unknown key: {keyname} ({key})')
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument('--color-test', action='store_true')
parser.add_argument('filename', nargs='?')
args = parser.parse_args()
curses.wrapper(c_main, args)
return 0
if __name__ == '__main__':
exit(main())