151 lines
4.6 KiB
Python
151 lines
4.6 KiB
Python
import argparse
|
|
import curses
|
|
import os
|
|
import re
|
|
import sys
|
|
from typing import List
|
|
from typing import Optional
|
|
from typing import Sequence
|
|
from typing import Tuple
|
|
|
|
from babi.buf import Buf
|
|
from babi.file import File
|
|
from babi.perf import Perf
|
|
from babi.perf import perf_log
|
|
from babi.screen import EditResult
|
|
from babi.screen import make_stdscr
|
|
from babi.screen import Screen
|
|
|
|
CONSOLE = 'CONIN$' if sys.platform == 'win32' else '/dev/tty'
|
|
POSITION_RE = re.compile(r'^\+-?\d+$')
|
|
|
|
|
|
def _edit(screen: Screen, stdin: str) -> EditResult:
|
|
screen.file.ensure_loaded(screen.status, screen.margin, stdin)
|
|
|
|
while True:
|
|
screen.status.tick(screen.margin)
|
|
screen.draw()
|
|
screen.file.move_cursor(screen.stdscr, screen.margin)
|
|
|
|
key = screen.get_char()
|
|
if key.keyname in File.DISPATCH:
|
|
File.DISPATCH[key.keyname](screen.file, screen.margin)
|
|
elif key.keyname in Screen.DISPATCH:
|
|
ret = Screen.DISPATCH[key.keyname](screen)
|
|
if isinstance(ret, EditResult):
|
|
return ret
|
|
elif key.keyname == b'STRING':
|
|
assert isinstance(key.wch, str), key.wch
|
|
screen.file.c(key.wch, screen.margin)
|
|
else:
|
|
screen.status.update(f'unknown key: {key}')
|
|
|
|
|
|
def c_main(
|
|
stdscr: 'curses._CursesWindow',
|
|
filenames: List[Optional[str]],
|
|
positions: List[int],
|
|
stdin: str,
|
|
perf: Perf,
|
|
) -> int:
|
|
screen = Screen(stdscr, filenames, positions, perf)
|
|
with screen.history.save():
|
|
while screen.files:
|
|
screen.i = screen.i % len(screen.files)
|
|
res = _edit(screen, stdin)
|
|
if res == EditResult.EXIT:
|
|
del screen.files[screen.i]
|
|
# always go to the next file except at the end
|
|
screen.i = min(screen.i, len(screen.files) - 1)
|
|
screen.status.clear()
|
|
elif res == EditResult.NEXT:
|
|
screen.i += 1
|
|
screen.status.clear()
|
|
elif res == EditResult.PREV:
|
|
screen.i -= 1
|
|
screen.status.clear()
|
|
elif res == EditResult.OPEN:
|
|
screen.i = len(screen.files) - 1
|
|
else:
|
|
raise AssertionError(f'unreachable {res}')
|
|
return 0
|
|
|
|
|
|
def _key_debug(stdscr: 'curses._CursesWindow', perf: Perf) -> int:
|
|
screen = Screen(stdscr, ['<<key debug>>'], [0], perf)
|
|
screen.file.buf = Buf([''])
|
|
|
|
while True:
|
|
screen.status.update('press q to quit')
|
|
screen.draw()
|
|
screen.file.move_cursor(screen.stdscr, screen.margin)
|
|
|
|
key = screen.get_char()
|
|
screen.file.buf.insert(-1, f'{key.wch!r} {key.keyname.decode()!r}')
|
|
screen.file.down(screen.margin)
|
|
if key.wch == curses.KEY_RESIZE:
|
|
screen.resize()
|
|
if key.wch == 'q':
|
|
return 0
|
|
|
|
|
|
def _filenames(filenames: List[str]) -> Tuple[List[Optional[str]], List[int]]:
|
|
if not filenames:
|
|
return [None], [0]
|
|
|
|
ret_filenames: List[Optional[str]] = []
|
|
ret_positions = []
|
|
|
|
filenames_iter = iter(filenames)
|
|
for filename in filenames_iter:
|
|
if POSITION_RE.match(filename):
|
|
# in the success case we get:
|
|
#
|
|
# position_s = +...
|
|
# filename = (the next thing)
|
|
#
|
|
# in the error case we only need to reset `position_s` as
|
|
# `filename` is already correct
|
|
position_s = filename
|
|
try:
|
|
filename = next(filenames_iter)
|
|
except StopIteration:
|
|
position_s = '+0'
|
|
ret_positions.append(int(position_s[1:]))
|
|
ret_filenames.append(filename)
|
|
else:
|
|
ret_positions.append(0)
|
|
ret_filenames.append(filename)
|
|
|
|
return ret_filenames, ret_positions
|
|
|
|
|
|
def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('filenames', metavar='filename', nargs='*')
|
|
parser.add_argument('--perf-log')
|
|
parser.add_argument(
|
|
'--key-debug', action='store_true', help=argparse.SUPPRESS,
|
|
)
|
|
args = parser.parse_args(argv)
|
|
|
|
if '-' in args.filenames:
|
|
print('reading stdin...', file=sys.stderr)
|
|
stdin = sys.stdin.read()
|
|
tty = os.open(CONSOLE, os.O_RDONLY)
|
|
os.dup2(tty, sys.stdin.fileno())
|
|
else:
|
|
stdin = ''
|
|
|
|
with perf_log(args.perf_log) as perf, make_stdscr() as stdscr:
|
|
if args.key_debug:
|
|
return _key_debug(stdscr, perf)
|
|
else:
|
|
filenames, positions = _filenames(args.filenames)
|
|
return c_main(stdscr, filenames, positions, stdin, perf)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
exit(main())
|