Add :expandtabs and :noexpandtabs
This commit is contained in:
@@ -59,6 +59,7 @@ class DelModification(NamedTuple):
|
|||||||
class Buf:
|
class Buf:
|
||||||
def __init__(self, lines: List[str], tab_size: int = 4) -> None:
|
def __init__(self, lines: List[str], tab_size: int = 4) -> None:
|
||||||
self._lines = lines
|
self._lines = lines
|
||||||
|
self.expandtabs = True
|
||||||
self.tab_size = tab_size
|
self.tab_size = tab_size
|
||||||
self.file_y = self.y = self._x = self._x_hint = 0
|
self.file_y = self.y = self._x = self._x_hint = 0
|
||||||
|
|
||||||
@@ -242,6 +243,13 @@ class Buf:
|
|||||||
|
|
||||||
# rendered lines
|
# rendered lines
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tab_string(self) -> str:
|
||||||
|
if self.expandtabs:
|
||||||
|
return ' ' * self.tab_size
|
||||||
|
else:
|
||||||
|
return '\t'
|
||||||
|
|
||||||
def rendered_line(self, idx: int, margin: Margin) -> str:
|
def rendered_line(self, idx: int, margin: Margin) -> str:
|
||||||
x = self._cursor_x if idx == self.y else 0
|
x = self._cursor_x if idx == self.y else 0
|
||||||
expanded = self._lines[idx].expandtabs(self.tab_size)
|
expanded = self._lines[idx].expandtabs(self.tab_size)
|
||||||
|
|||||||
23
babi/file.py
23
babi/file.py
@@ -524,20 +524,29 @@ class File:
|
|||||||
assert self.selection.start is not None
|
assert self.selection.start is not None
|
||||||
sel_y, sel_x = self.selection.start
|
sel_y, sel_x = self.selection.start
|
||||||
(s_y, _), (e_y, _) = self.selection.get()
|
(s_y, _), (e_y, _) = self.selection.get()
|
||||||
|
tab_string = self.buf.tab_string
|
||||||
|
tab_size = len(tab_string)
|
||||||
for l_y in range(s_y, e_y + 1):
|
for l_y in range(s_y, e_y + 1):
|
||||||
if self.buf[l_y]:
|
if self.buf[l_y]:
|
||||||
self.buf[l_y] = ' ' * self.buf.tab_size + self.buf[l_y]
|
self.buf[l_y] = tab_string + self.buf[l_y]
|
||||||
if l_y == self.buf.y:
|
if l_y == self.buf.y:
|
||||||
self.buf.x += self.buf.tab_size
|
self.buf.x += tab_size
|
||||||
if l_y == sel_y and sel_x != 0:
|
if l_y == sel_y and sel_x != 0:
|
||||||
sel_x += self.buf.tab_size
|
sel_x += tab_size
|
||||||
self.selection.set(sel_y, sel_x, self.buf.y, self.buf.x)
|
self.selection.set(sel_y, sel_x, self.buf.y, self.buf.x)
|
||||||
|
|
||||||
@edit_action('insert tab', final=False)
|
@edit_action('insert tab', final=False)
|
||||||
def _tab(self, margin: Margin) -> None:
|
def _tab(self, margin: Margin) -> None:
|
||||||
n = self.buf.tab_size - self.buf.x % self.buf.tab_size
|
tab_string = self.buf.tab_string
|
||||||
|
if tab_string == '\t':
|
||||||
|
n = 1
|
||||||
|
else:
|
||||||
|
n = self.buf.tab_size - self.buf.x % self.buf.tab_size
|
||||||
|
tab_string = tab_string[:n]
|
||||||
line = self.buf[self.buf.y]
|
line = self.buf[self.buf.y]
|
||||||
self.buf[self.buf.y] = line[:self.buf.x] + n * ' ' + line[self.buf.x:]
|
self.buf[self.buf.y] = (
|
||||||
|
line[:self.buf.x] + tab_string + line[self.buf.x:]
|
||||||
|
)
|
||||||
self.buf.x += n
|
self.buf.x += n
|
||||||
self.buf.restore_eof_invariant()
|
self.buf.restore_eof_invariant()
|
||||||
|
|
||||||
@@ -548,9 +557,9 @@ class File:
|
|||||||
self._tab(margin)
|
self._tab(margin)
|
||||||
|
|
||||||
def _dedent_line(self, s: str) -> int:
|
def _dedent_line(self, s: str) -> int:
|
||||||
bound = min(len(s), self.buf.tab_size)
|
bound = min(len(s), len(self.buf.tab_string))
|
||||||
i = 0
|
i = 0
|
||||||
while i < bound and s[i] == ' ':
|
while i < bound and s[i] in (' ', '\t'):
|
||||||
i += 1
|
i += 1
|
||||||
return i
|
return i
|
||||||
|
|
||||||
|
|||||||
@@ -457,6 +457,14 @@ class Screen:
|
|||||||
for file in self.files:
|
for file in self.files:
|
||||||
file.buf.set_tab_size(parsed_tab_size)
|
file.buf.set_tab_size(parsed_tab_size)
|
||||||
self.status.update('updated!')
|
self.status.update('updated!')
|
||||||
|
elif response.startswith(':expandtabs'):
|
||||||
|
for file in self.files:
|
||||||
|
file.buf.expandtabs = True
|
||||||
|
self.status.update('updated!')
|
||||||
|
elif response.startswith(':noexpandtabs'):
|
||||||
|
for file in self.files:
|
||||||
|
file.buf.expandtabs = False
|
||||||
|
self.status.update('updated!')
|
||||||
elif response == ':comment' or response.startswith(':comment '):
|
elif response == ':comment' or response.startswith(':comment '):
|
||||||
_, _, comment = response.partition(' ')
|
_, _, comment = response.partition(' ')
|
||||||
comment = (comment or '#').strip()
|
comment = (comment or '#').strip()
|
||||||
|
|||||||
45
tests/features/expandtabs_test.py
Normal file
45
tests/features/expandtabs_test.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
from testing.runner import and_exit
|
||||||
|
from testing.runner import trigger_command_mode
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_expandtabs(run, tmpdir):
|
||||||
|
f = tmpdir.join('f')
|
||||||
|
f.write('a')
|
||||||
|
|
||||||
|
with run(str(f)) as h, and_exit(h):
|
||||||
|
h.press('Left')
|
||||||
|
trigger_command_mode(h)
|
||||||
|
h.press_and_enter(':expandtabs')
|
||||||
|
h.await_text('updated!')
|
||||||
|
h.press('Tab')
|
||||||
|
h.press('^S')
|
||||||
|
assert f.read() == ' a\n'
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_noexpandtabs(run, tmpdir):
|
||||||
|
f = tmpdir.join('f')
|
||||||
|
f.write('a')
|
||||||
|
|
||||||
|
with run(str(f)) as h, and_exit(h):
|
||||||
|
h.press('Left')
|
||||||
|
trigger_command_mode(h)
|
||||||
|
h.press_and_enter(':noexpandtabs')
|
||||||
|
h.await_text('updated!')
|
||||||
|
h.press('Tab')
|
||||||
|
h.press('^S')
|
||||||
|
assert f.read() == '\ta\n'
|
||||||
|
|
||||||
|
|
||||||
|
def test_indent_with_expandtabs(run, tmpdir):
|
||||||
|
f = tmpdir.join('f')
|
||||||
|
f.write('a\nb\nc')
|
||||||
|
|
||||||
|
with run(str(f)) as h, and_exit(h):
|
||||||
|
trigger_command_mode(h)
|
||||||
|
h.press_and_enter(':noexpandtabs')
|
||||||
|
h.await_text('updated!')
|
||||||
|
for _ in range(3):
|
||||||
|
h.press('S-Down')
|
||||||
|
h.press('Tab')
|
||||||
|
h.press('^S')
|
||||||
|
assert f.read() == '\ta\n\tb\n\tc\n'
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
from testing.runner import and_exit
|
from testing.runner import and_exit
|
||||||
|
from testing.runner import trigger_command_mode
|
||||||
|
|
||||||
|
|
||||||
def test_indent_at_beginning_of_line(run):
|
def test_indent_at_beginning_of_line(run):
|
||||||
@@ -87,6 +88,20 @@ def test_dedent_selection(run, tmpdir):
|
|||||||
h.await_text('\n1\n2\n 3\n')
|
h.await_text('\n1\n2\n 3\n')
|
||||||
|
|
||||||
|
|
||||||
|
def test_dedent_selection_with_noexpandtabs(run, tmpdir):
|
||||||
|
f = tmpdir.join('f')
|
||||||
|
f.write('1\n\t2\n\t\t3\n')
|
||||||
|
with run(str(f)) as h, and_exit(h):
|
||||||
|
trigger_command_mode(h)
|
||||||
|
h.press_and_enter(':noexpandtabs')
|
||||||
|
h.await_text('updated!')
|
||||||
|
for _ in range(3):
|
||||||
|
h.press('S-Down')
|
||||||
|
h.press('BTab')
|
||||||
|
h.press('^S')
|
||||||
|
assert f.read() == '1\n2\n\t3\n'
|
||||||
|
|
||||||
|
|
||||||
def test_dedent_beginning_of_line(run, tmpdir):
|
def test_dedent_beginning_of_line(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write(' hi\n')
|
f.write(' hi\n')
|
||||||
|
|||||||
Reference in New Issue
Block a user