Files
babi/babi/main.py
Anthony Sottile 4812daf300 Implement open-with-offset
Resolves #60
2020-04-17 19:31:51 -07:00

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())