Add indent / dedent

Resolves #27
This commit is contained in:
Anthony Sottile
2020-01-05 09:00:28 -08:00
parent b08f533554
commit 22db250ab8
2 changed files with 165 additions and 0 deletions

64
babi.py
View File

@@ -930,6 +930,68 @@ class File:
self._increment_y(margin)
self.x = self.x_hint = 0
@edit_action('indent selection', final=True)
def _indent_selection(self, margin: Margin) -> None:
assert self.select_start is not None
sel_y, sel_x = self.select_start
(s_y, _), (e_y, _) = self._get_selection()
for l_y in range(s_y, e_y + 1):
if self.lines[l_y]:
self.lines[l_y] = ' ' * 4 + self.lines[l_y]
if l_y == sel_y and sel_x != 0:
self.select_start = (sel_y, sel_x + 4)
if l_y == self.y:
self.x = self.x_hint = self.x + 4
@edit_action('insert tab', final=False)
def _tab(self, margin: Margin) -> None:
n = 4 - self.x % 4
line = self.lines[self.y]
self.lines[self.y] = line[:self.x] + n * ' ' + line[self.x:]
self.x = self.x_hint = self.x + n
_restore_lines_eof_invariant(self.lines)
def tab(self, margin: Margin) -> None:
if self.select_start:
self._indent_selection(margin)
else:
self._tab(margin)
@staticmethod
def _dedent_line(s: str) -> int:
bound = min(len(s), 4)
i = 0
while i < bound and s[i] == ' ':
i += 1
return i
@edit_action('dedent selection', final=True)
def _dedent_selection(self, margin: Margin) -> None:
assert self.select_start is not None
sel_y, sel_x = self.select_start
(s_y, _), (e_y, _) = self._get_selection()
for l_y in range(s_y, e_y + 1):
n = self._dedent_line(self.lines[l_y])
if n:
self.lines[l_y] = self.lines[l_y][n:]
if l_y == sel_y:
self.select_start = (sel_y, max(sel_x - n, 0))
if l_y == self.y:
self.x = self.x_hint = max(self.x - n, 0)
@edit_action('dedent', final=True)
def _dedent(self, margin: Margin) -> None:
n = self._dedent_line(self.lines[self.y])
if n:
self.lines[self.y] = self.lines[self.y][n:]
self.x = self.x_hint = self.x - n
def shift_tab(self, margin: Margin) -> None:
if self.select_start:
self._dedent_selection(margin)
else:
self._dedent(margin)
@edit_action('cut selection', final=True)
@clear_selection
def cut_selection(self, margin: Margin) -> Tuple[str, ...]:
@@ -1015,6 +1077,8 @@ class File:
b'^H': backspace, # ^Backspace
b'KEY_DC': delete,
b'^M': enter,
b'^I': tab,
b'KEY_BTAB': shift_tab,
# selection (shift + movement)
b'KEY_SR': keep_selection(up),
b'KEY_SF': keep_selection(down),

101
tests/indent_test.py Normal file
View File

@@ -0,0 +1,101 @@
from testing.runner import and_exit
from testing.runner import run
def test_indent_at_beginning_of_line():
with run() as h, and_exit(h):
h.press('hello')
h.press('Home')
h.press('Tab')
h.await_text('\n hello\n')
h.await_cursor_position(x=4, y=1)
def test_indent_not_full_tab():
with run() as h, and_exit(h):
h.press('h')
h.press('Tab')
h.press('ello')
h.await_text('h ello')
h.await_cursor_position(x=8, y=1)
def test_indent_fixes_eof():
with run() as h, and_exit(h):
h.press('Tab')
h.press('Down')
h.await_cursor_position(x=0, y=2)
def test_indent_selection(ten_lines):
with run(str(ten_lines)) as h, and_exit(h):
h.press('S-Right')
h.press('Tab')
h.await_text('\n line_0\n')
h.await_cursor_position(x=5, y=1)
h.press('^K')
h.await_text('\nine_0\n')
def test_indent_selection_does_not_extend_mid_line_selection(ten_lines):
with run(str(ten_lines)) as h, and_exit(h):
h.press('Right')
h.press('S-Right')
h.press('Tab')
h.await_text('\n line_0\n')
h.await_cursor_position(x=6, y=1)
h.press('^K')
h.await_text('\n lne_0\n')
def test_indent_selection_leaves_blank_lines(tmpdir):
f = tmpdir.join('f')
f.write('1\n\n2\n\n3\n')
with run(str(f)) as h, and_exit(h):
for _ in range(3):
h.press('S-Down')
h.press('Tab')
h.press('^S')
assert f.read() == ' 1\n\n 2\n\n3\n'
def test_dedent_no_indentation():
with run() as h, and_exit(h):
h.press('a')
h.press('BTab')
h.await_text('\na\n')
h.await_cursor_position(x=1, y=1)
def test_dedent_exactly_one_indent():
with run() as h, and_exit(h):
h.press('Tab')
h.press('a')
h.await_text('\n a\n')
h.press('BTab')
h.await_text('\na\n')
h.await_cursor_position(x=1, y=1)
def test_dedent_selection(tmpdir):
f = tmpdir.join('f')
f.write('1\n 2\n 3\n')
with run(str(f)) as h, and_exit(h):
for _ in range(3):
h.press('S-Down')
h.press('BTab')
h.await_text('\n1\n2\n 3\n')
def test_dedent_selection_does_not_make_selection_negative():
with run() as h, and_exit(h):
h.press('Tab')
h.press('hello')
h.press('Home')
h.press('Right')
h.press('S-Right')
h.press('BTab')
h.await_text('\nhello\n')
h.press('S-Right')
h.press('^K')
h.await_text('\nello\n')