Add :sort command

This commit is contained in:
Anthony Sottile
2020-01-06 09:23:53 -08:00
parent 083417399e
commit 180ff20be5
4 changed files with 85 additions and 11 deletions

28
babi.py
View File

@@ -6,6 +6,7 @@ import enum
import functools
import hashlib
import io
import itertools
import os
import re
import signal
@@ -1051,6 +1052,27 @@ class File:
self.x = self.x_hint = len(self.lines[self.y])
self.lines[self.y] += self.lines.pop(self.y + 1)
def _sort(self, margin: Margin, s_y: int, e_y: int) -> None:
# self.lines intentionally does not support slicing so we use islice
lines = sorted(itertools.islice(self.lines, s_y, e_y))
for i, line in zip(range(s_y, e_y), lines):
self.lines[i] = line
self.y = s_y
self.x = self.x_hint = 0
self.scroll_screen_if_needed(margin)
@edit_action('sort', final=True)
def sort(self, margin: Margin) -> None:
self._sort(margin, 0, len(self.lines) - 1)
@edit_action('sort selection', final=True)
@clear_selection
def sort_selection(self, margin: Margin) -> None:
(s_y, _), (e_y, _) = self._get_selection()
e_y = min(e_y + 1, len(self.lines) - 1)
self._sort(margin, s_y, e_y)
DISPATCH = {
# movement
b'KEY_UP': up,
@@ -1448,6 +1470,12 @@ class Screen:
elif response == ':wq':
self.save()
return EditResult.EXIT
elif response == ':sort':
if self.file.select_start:
self.file.sort_selection(self.margin)
else:
self.file.sort(self.margin)
self.status.update('sorted!')
elif response is not PromptResult.CANCELLED:
self.status.update(f'invalid command: {response}')
return None

View File

@@ -143,3 +143,14 @@ def and_exit(h):
if ' *' in h.get_screen_line(0):
h.press('n')
h.await_exit()
def trigger_command_mode(h):
# in order to enter a steady state, trigger an unknown key first and then
# press escape to open the command mode. this is necessary as `Escape` is
# the start of "escape sequences" and sending characters too quickly will
# be interpreted as a single keypress
h.press('^J')
h.await_text('unknown key')
h.press('Escape')
h.await_text_missing('unknown key')

View File

@@ -2,17 +2,7 @@ import pytest
from testing.runner import and_exit
from testing.runner import run
def trigger_command_mode(h):
# in order to enter a steady state, trigger an unknown key first and then
# press escape to open the command mode. this is necessary as `Escape` is
# the start of "escape sequences" and sending characters too quickly will
# be interpreted as a single keypress
h.press('^J')
h.await_text('unknown key')
h.press('Escape')
h.await_text_missing('unknown key')
from testing.runner import trigger_command_mode
def test_quit_via_colon_q():

45
tests/sort_test.py Normal file
View File

@@ -0,0 +1,45 @@
import pytest
from testing.runner import and_exit
from testing.runner import run
from testing.runner import trigger_command_mode
@pytest.fixture
def unsorted(tmpdir):
f = tmpdir.join('f')
f.write('d\nb\nc\na\n')
return f
def test_sort_entire_file(unsorted):
with run(str(unsorted)) as h, and_exit(h):
trigger_command_mode(h)
h.press_and_enter(':sort')
h.await_text('sorted!')
h.await_cursor_position(x=0, y=1)
h.press('^S')
assert unsorted.read() == 'a\nb\nc\nd\n'
def test_sort_selection(unsorted):
with run(str(unsorted)) as h, and_exit(h):
h.press('S-Down')
trigger_command_mode(h)
h.press_and_enter(':sort')
h.await_text('sorted!')
h.await_cursor_position(x=0, y=1)
h.press('^S')
assert unsorted.read() == 'b\nd\nc\na\n'
def test_sort_selection_does_not_include_eof(unsorted):
with run(str(unsorted)) as h, and_exit(h):
for _ in range(5):
h.press('S-Down')
trigger_command_mode(h)
h.press_and_enter(':sort')
h.await_text('sorted!')
h.await_cursor_position(x=0, y=1)
h.press('^S')
assert unsorted.read() == 'a\nb\nc\nd\n'