Implement go to line (^_)

This commit is contained in:
Anthony Sottile
2019-11-14 17:55:26 -08:00
parent 38a9a737b6
commit 795be3c5ca
3 changed files with 126 additions and 53 deletions

37
babi.py
View File

@@ -232,11 +232,18 @@ class Status:
pos = 0
buf = ''
while True:
width = curses.COLS - len(prompt)
cmd = f'{prompt}{_scrolled_line(buf, pos, width, current=True)}'
if not prompt or curses.COLS < 7:
prompt_s = ''
elif len(prompt) > curses.COLS - 6:
prompt_s = f'{prompt[:curses.COLS - 7]}…: '
else:
prompt_s = f'{prompt}: '
width = curses.COLS - len(prompt_s)
cmd = f'{prompt_s}{_scrolled_line(buf, pos, width, current=True)}'
screen.stdscr.insstr(curses.LINES - 1, 0, cmd, curses.A_REVERSE)
line_x = _line_x(pos, width)
screen.stdscr.move(curses.LINES - 1, pos - line_x)
screen.stdscr.move(curses.LINES - 1, len(prompt_s) + pos - line_x)
key = _get_char(screen.stdscr)
if key.key == curses.KEY_RESIZE:
@@ -494,6 +501,19 @@ class File:
self.cursor_y = len(self.lines) - 1
self._scroll_screen_if_needed(margin)
@action
def go_to_line(self, lineno: int, margin: Margin) -> None:
self.x = self.x_hint = 0
if lineno == 0:
self.cursor_y = 0
elif lineno > len(self.lines):
self.cursor_y = len(self.lines) - 1
elif lineno < 0:
self.cursor_y = max(0, lineno + len(self.lines))
else:
self.cursor_y = lineno - 1
self._scroll_screen_if_needed(margin)
@action
def page_up(self, margin: Margin) -> None:
if self.cursor_y < margin.body_lines:
@@ -864,6 +884,17 @@ def _edit(screen: Screen) -> EditResult:
screen.file.undo(screen.status, screen.margin)
elif key.keyname == b'M-U':
screen.file.redo(screen.status, screen.margin)
elif key.keyname == b'^_':
response = screen.status.prompt(screen, 'enter line number')
if response == '':
screen.status.update('cancelled')
else:
try:
lineno = int(response)
except ValueError:
screen.status.update(f'not an integer: {response!r}')
else:
screen.file.go_to_line(lineno, screen.margin)
elif key.keyname == b'^[': # escape
response = screen.status.prompt(screen, '')
if response == ':q':

View File

@@ -1,4 +1,4 @@
coverage
git+https://github.com/mjsir911/hecate@092f811
git+https://github.com/asottile/hecate@ebe6dfb
pytest
remote-pdb

View File

@@ -337,6 +337,13 @@ def trigger_command_mode(h):
h.await_text_missing('unknown key')
@pytest.fixture
def ten_lines(tmpdir):
f = tmpdir.join('f')
f.write('\n'.join(f'line_{i}' for i in range(10)))
yield f
@pytest.mark.parametrize('color', (True, False))
def test_color_test(color):
with run('--color-test', color=color) as h, and_exit(h):
@@ -486,11 +493,8 @@ def test_arrow_key_movement(tmpdir):
('page_up', 'page_down'),
(('PageUp', 'PageDown'), ('^Y', '^V')),
)
def test_page_up_and_page_down(tmpdir, page_up, page_down):
f = tmpdir.join('f')
f.write('\n'.join(f'line_{i}' for i in range(10)))
with run(str(f), height=10) as h, and_exit(h):
def test_page_up_and_page_down(ten_lines, page_up, page_down):
with run(str(ten_lines), height=10) as h, and_exit(h):
h.press('Down')
h.press('Down')
h.press(page_up)
@@ -515,11 +519,8 @@ def test_page_up_and_page_down(tmpdir, page_up, page_down):
assert h.get_cursor_line() == 'line_9'
def test_page_up_page_down_size_small_window(tmpdir):
f = tmpdir.join('f')
f.write('\n'.join(f'line_{i}' for i in range(10)))
with run(str(f), height=4) as h, and_exit(h):
def test_page_up_page_down_size_small_window(ten_lines):
with run(str(ten_lines), height=4) as h, and_exit(h):
h.press('PageDown')
h.await_text('line_2')
h.await_cursor_position(x=0, y=1)
@@ -532,11 +533,8 @@ def test_page_up_page_down_size_small_window(tmpdir):
assert h.get_cursor_line() == 'line_0'
def test_ctrl_home(tmpdir):
f = tmpdir.join('f')
f.write('\n'.join(f'line_{i}' for i in range(10)))
with run(str(f), height=4) as h, and_exit(h):
def test_ctrl_home(ten_lines):
with run(str(ten_lines), height=4) as h, and_exit(h):
for _ in range(3):
h.press('PageDown')
h.await_text_missing('line_0')
@@ -546,21 +544,15 @@ def test_ctrl_home(tmpdir):
h.await_cursor_position(x=0, y=1)
def test_ctrl_end(tmpdir):
f = tmpdir.join('f')
f.write('\n'.join(f'line_{i}' for i in range(10)))
with run(str(f), height=6) as h, and_exit(h):
def test_ctrl_end(ten_lines):
with run(str(ten_lines), height=6) as h, and_exit(h):
h.press('^End')
h.await_cursor_position(x=0, y=3)
assert h.get_screen_line(2) == 'line_9'
def test_ctrl_end_already_on_last_page(tmpdir):
f = tmpdir.join('f')
f.write('\n'.join(f'line_{i}' for i in range(10)))
with run(str(f), height=9) as h, and_exit(h):
def test_ctrl_end_already_on_last_page(ten_lines):
with run(str(ten_lines), height=9) as h, and_exit(h):
h.press('PageDown')
h.await_cursor_position(x=0, y=1)
h.await_text('line_9')
@@ -570,11 +562,73 @@ def test_ctrl_end_already_on_last_page(tmpdir):
assert h.get_screen_line(5) == 'line_9'
def test_scrolling_arrow_key_movement(tmpdir):
f = tmpdir.join('f')
f.write('\n'.join(f'line_{i}' for i in range(10)))
def test_prompt_window_width():
with run() as h, and_exit(h):
h.press('^_')
h.await_text('enter line number:')
h.press('123')
with h.resize(width=23, height=24):
h.await_text('\nenter line number: «3')
with h.resize(width=22, height=24):
h.await_text('\nenter line numb…: «3')
with h.resize(width=7, height=24):
h.await_text('\n…: «3')
with h.resize(width=6, height=24):
h.await_text('\n123')
h.press('Enter')
with run(str(f), height=10) as h, and_exit(h):
def test_go_to_line_line(ten_lines):
def _jump_to_line(n):
h.press('^_')
h.await_text('enter line number:')
h.press_and_enter(str(n))
h.await_text_missing('enter line number:')
with run(str(ten_lines), height=9) as h, and_exit(h):
# still on screen
_jump_to_line(3)
h.await_cursor_position(x=0, y=3)
# should go to beginning of file
_jump_to_line(0)
h.await_cursor_position(x=0, y=1)
# should go to end of the file
_jump_to_line(999)
h.await_cursor_position(x=0, y=4)
assert h.get_screen_line(3) == 'line_9'
# should also go to the end of the file
_jump_to_line(-1)
h.await_cursor_position(x=0, y=4)
assert h.get_screen_line(3) == 'line_9'
# should go to beginning of file
_jump_to_line(-999)
h.await_cursor_position(x=0, y=1)
assert h.get_cursor_line() == 'line_0'
@pytest.mark.parametrize('key', ('Enter', '^C'))
def test_go_to_line_cancel(ten_lines, key):
with run(str(ten_lines)) as h, and_exit(h):
h.press('Down')
h.await_cursor_position(x=0, y=2)
h.press('^_')
h.await_text('enter line number:')
h.press(key)
h.await_cursor_position(x=0, y=2)
h.await_text('cancelled')
def test_go_to_line_not_an_integer():
with run() as h, and_exit(h):
h.press('^_')
h.await_text('enter line number:')
h.press_and_enter('asdf')
h.await_text("not an integer: 'asdf'")
def test_scrolling_arrow_key_movement(ten_lines):
with run(str(ten_lines), height=10) as h, and_exit(h):
h.await_text('line_7')
# we should not have scrolled after 7 presses
for _ in range(7):
@@ -625,11 +679,8 @@ def test_home_key(tmpdir, k):
h.await_cursor_position(x=0, y=2)
def test_resize_scrolls_up(tmpdir):
f = tmpdir.join('f')
f.write('\n'.join(f'line_{i}' for i in range(10)))
with run(str(f)) as h, and_exit(h):
def test_resize_scrolls_up(ten_lines):
with run(str(ten_lines)) as h, and_exit(h):
h.await_text('line_9')
for _ in range(7):
@@ -651,11 +702,8 @@ def test_resize_scrolls_up(tmpdir):
assert h.get_cursor_line() == 'line_7'
def test_resize_scroll_does_not_go_negative(tmpdir):
f = tmpdir.join('f')
f.write('\n'.join(f'line_{i}' for i in range(10)))
with run(str(f)) as h, and_exit(h):
def test_resize_scroll_does_not_go_negative(ten_lines):
with run(str(ten_lines)) as h, and_exit(h):
for _ in range(5):
h.press('Down')
h.await_cursor_position(x=0, y=6)
@@ -670,11 +718,8 @@ def test_resize_scroll_does_not_go_negative(tmpdir):
assert h.get_screen_line(1) == 'line_0'
def test_page_up_does_not_go_negative(tmpdir):
f = tmpdir.join('f')
f.write('\n'.join(f'line_{i}' for i in range(10)))
with run(str(f), height=10) as h, and_exit(h):
def test_page_up_does_not_go_negative(ten_lines):
with run(str(ten_lines), height=10) as h, and_exit(h):
for _ in range(8):
h.press('Down')
h.await_cursor_position(x=0, y=4)
@@ -1147,11 +1192,8 @@ def test_cancel_command_mode():
h.await_text_missing('invalid command')
def test_cut_and_uncut(tmpdir):
f = tmpdir.join('f')
f.write('\n'.join(f'line_{i}' for i in range(10)))
with run(str(f)) as h, and_exit(h):
def test_cut_and_uncut(ten_lines):
with run(str(ten_lines)) as h, and_exit(h):
h.press('^K')
h.await_text_missing('line_0')
h.await_text(' *')