4 Commits
wc ... v0.0.8

Author SHA1 Message Date
Anthony Sottile
3f259403fe v0.0.8 2020-05-08 15:56:59 -07:00
Anthony Sottile
4b27a18c0f Merge pull request #61 from theendlessriver13/fix_CTL_HOME_END_on_win
fix jump to top/end of file on windows
2020-05-08 15:56:16 -07:00
Jonas Kittner
58bc4780ca fix jump to top/end of file on windows 2020-05-09 00:47:18 +02:00
Anthony Sottile
4812daf300 Implement open-with-offset
Resolves #60
2020-04-17 19:31:51 -07:00
7 changed files with 130 additions and 34 deletions

View File

@@ -198,10 +198,12 @@ class File:
def __init__( def __init__(
self, self,
filename: Optional[str], filename: Optional[str],
initial_line: int,
color_manager: ColorManager, color_manager: ColorManager,
hl_factories: Tuple[HLFactory, ...], hl_factories: Tuple[HLFactory, ...],
) -> None: ) -> None:
self.filename = filename self.filename = filename
self.initial_line = initial_line
self.modified = False self.modified = False
self.buf = Buf([]) self.buf = Buf([])
self.nl = '\n' self.nl = '\n'
@@ -215,7 +217,12 @@ class File:
self.selection = Selection() self.selection = Selection()
self._file_hls: Tuple[FileHL, ...] = () self._file_hls: Tuple[FileHL, ...] = ()
def ensure_loaded(self, status: Status, stdin: str) -> None: def ensure_loaded(
self,
status: Status,
margin: Margin,
stdin: str,
) -> None:
if self.buf: if self.buf:
return return
@@ -257,6 +264,8 @@ class File:
for file_hl in self._file_hls: for file_hl in self._file_hls:
file_hl.register_callbacks(self.buf) file_hl.register_callbacks(self.buf)
self.go_to_line(self.initial_line, margin)
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<{type(self).__name__} {self.filename!r}>' return f'<{type(self).__name__} {self.filename!r}>'

View File

@@ -1,9 +1,12 @@
import argparse import argparse
import curses import curses
import os import os
import re
import sys import sys
from typing import List
from typing import Optional from typing import Optional
from typing import Sequence from typing import Sequence
from typing import Tuple
from babi.buf import Buf from babi.buf import Buf
from babi.file import File from babi.file import File
@@ -14,10 +17,11 @@ from babi.screen import make_stdscr
from babi.screen import Screen from babi.screen import Screen
CONSOLE = 'CONIN$' if sys.platform == 'win32' else '/dev/tty' CONSOLE = 'CONIN$' if sys.platform == 'win32' else '/dev/tty'
POSITION_RE = re.compile(r'^\+-?\d+$')
def _edit(screen: Screen, stdin: str) -> EditResult: def _edit(screen: Screen, stdin: str) -> EditResult:
screen.file.ensure_loaded(screen.status, stdin) screen.file.ensure_loaded(screen.status, screen.margin, stdin)
while True: while True:
screen.status.tick(screen.margin) screen.status.tick(screen.margin)
@@ -40,11 +44,12 @@ def _edit(screen: Screen, stdin: str) -> EditResult:
def c_main( def c_main(
stdscr: 'curses._CursesWindow', stdscr: 'curses._CursesWindow',
args: argparse.Namespace, filenames: List[Optional[str]],
positions: List[int],
stdin: str, stdin: str,
perf: Perf,
) -> int: ) -> int:
with perf_log(args.perf_log) as perf: screen = Screen(stdscr, filenames, positions, perf)
screen = Screen(stdscr, args.filenames or [None], perf)
with screen.history.save(): with screen.history.save():
while screen.files: while screen.files:
screen.i = screen.i % len(screen.files) screen.i = screen.i % len(screen.files)
@@ -67,8 +72,8 @@ def c_main(
return 0 return 0
def _key_debug(stdscr: 'curses._CursesWindow') -> int: def _key_debug(stdscr: 'curses._CursesWindow', perf: Perf) -> int:
screen = Screen(stdscr, ['<<key debug>>'], Perf()) screen = Screen(stdscr, ['<<key debug>>'], [0], perf)
screen.file.buf = Buf(['']) screen.file.buf = Buf([''])
while True: while True:
@@ -85,6 +90,37 @@ def _key_debug(stdscr: 'curses._CursesWindow') -> int:
return 0 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: def main(argv: Optional[Sequence[str]] = None) -> int:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('filenames', metavar='filename', nargs='*') parser.add_argument('filenames', metavar='filename', nargs='*')
@@ -102,11 +138,12 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
else: else:
stdin = '' stdin = ''
with make_stdscr() as stdscr: with perf_log(args.perf_log) as perf, make_stdscr() as stdscr:
if args.key_debug: if args.key_debug:
return _key_debug(stdscr) return _key_debug(stdscr, perf)
else: else:
return c_main(stdscr, args, stdin) filenames, positions = _filenames(args.filenames)
return c_main(stdscr, filenames, positions, stdin, perf)
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -81,6 +81,8 @@ KEYNAME_REWRITE = {
b'CTL_DOWN': b'kDN5', b'CTL_DOWN': b'kDN5',
b'CTL_RIGHT': b'kRIT5', b'CTL_RIGHT': b'kRIT5',
b'CTL_LEFT': b'kLFT5', b'CTL_LEFT': b'kLFT5',
b'CTL_HOME': b'kHOM5',
b'CTL_END': b'kEND5',
b'ALT_RIGHT': b'kRIT3', b'ALT_RIGHT': b'kRIT3',
b'ALT_LEFT': b'kLFT3', b'ALT_LEFT': b'kLFT3',
# windows-curses: idk why these are different # windows-curses: idk why these are different
@@ -103,14 +105,15 @@ class Screen:
self, self,
stdscr: 'curses._CursesWindow', stdscr: 'curses._CursesWindow',
filenames: List[Optional[str]], filenames: List[Optional[str]],
initial_lines: List[int],
perf: Perf, perf: Perf,
) -> None: ) -> None:
self.stdscr = stdscr self.stdscr = stdscr
self.color_manager = ColorManager.make() self.color_manager = ColorManager.make()
self.hl_factories = (Syntax.from_screen(stdscr, self.color_manager),) self.hl_factories = (Syntax.from_screen(stdscr, self.color_manager),)
self.files = [ self.files = [
File(filename, self.color_manager, self.hl_factories) File(filename, line, self.color_manager, self.hl_factories)
for filename in filenames for filename, line in zip(filenames, initial_lines)
] ]
self.i = 0 self.i = 0
self.history = History() self.history = History()
@@ -489,7 +492,7 @@ class Screen:
def open_file(self) -> Optional[EditResult]: def open_file(self) -> Optional[EditResult]:
response = self.prompt('enter filename', history='open') response = self.prompt('enter filename', history='open')
if response is not PromptResult.CANCELLED: if response is not PromptResult.CANCELLED:
opened = File(response, self.color_manager, self.hl_factories) opened = File(response, 0, self.color_manager, self.hl_factories)
self.files.append(opened) self.files.append(opened)
return EditResult.OPEN return EditResult.OPEN
else: else:

View File

@@ -1,6 +1,6 @@
[metadata] [metadata]
name = babi name = babi
version = 0.0.7 version = 0.0.8
description = a text editor description = a text editor
long_description = file: README.md long_description = file: README.md
long_description_content_type = text/markdown long_description_content_type = text/markdown

View File

@@ -0,0 +1,28 @@
from testing.runner import and_exit
def test_open_file_named_plus_something(run):
with run('+3') as h, and_exit(h):
h.await_text(' +3')
def test_initial_position_one_file(run, tmpdir):
f = tmpdir.join('f')
f.write('hello\nworld\n')
with run('+2', str(f)) as h, and_exit(h):
h.await_cursor_position(x=0, y=2)
def test_initial_position_multiple_files(run, tmpdir):
f = tmpdir.join('f')
f.write('1\n2\n3\n4\n')
g = tmpdir.join('g')
g.write('5\n6\n7\n8\n')
with run('+2', str(f), '+3', str(g)) as h, and_exit(h):
h.await_cursor_position(x=0, y=2)
h.press('^X')
h.await_cursor_position(x=0, y=3)

View File

@@ -8,7 +8,7 @@ from babi.file import get_lines
def test_position_repr(): def test_position_repr():
ret = repr(File('f.txt', ColorManager.make(), ())) ret = repr(File('f.txt', 0, ColorManager.make(), ()))
assert ret == "<File 'f.txt'>" assert ret == "<File 'f.txt'>"

19
tests/main_test.py Normal file
View File

@@ -0,0 +1,19 @@
import pytest
from babi import main
@pytest.mark.parametrize(
('in_filenames', 'expected_filenames', 'expected_positions'),
(
([], [None], [0]),
(['+3'], ['+3'], [0]),
(['f'], ['f'], [0]),
(['+3', 'f'], ['f'], [3]),
(['+-3', 'f'], ['f'], [-3]),
(['+3', '+3'], ['+3'], [3]),
(['+2', 'f', '+5', 'g'], ['f', 'g'], [2, 5]),
),
)
def test_filenames(in_filenames, expected_filenames, expected_positions):
filenames, positions = main._filenames(in_filenames)