Skip to content

Commit

Permalink
Tab-stop support when spaces used for indent
Browse files Browse the repository at this point in the history
  • Loading branch information
darrenburns committed Sep 20, 2023
1 parent a56eeea commit 03196ca
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 2 deletions.
25 changes: 23 additions & 2 deletions src/textual/widgets/_text_area.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ class TextArea(ScrollView, can_focus=True):
Changing this value will immediately re-render the `TextArea`."""

indent_width: Reactive[int] = reactive(4)
"""The width of tabs or the number of spaces to insert on pressing the `tab` key.
"""The width of tabs or the multiple of spaces to align to on pressing the `tab` key.
If the document currently open contains tabs that are currently visible on screen,
altering this value will immediately change the display width of the visible tabs.
Expand Down Expand Up @@ -916,7 +916,7 @@ async def _on_key(self, event: events.Key) -> None:
"""Handle key presses which correspond to document inserts."""
key = event.key
insert_values = {
"tab": " " * self.indent_width if self.indent_type == "spaces" else "\t",
"tab": " " * self._find_columns_to_next_tab_stop(),
"enter": "\n",
}
self._restart_blink()
Expand All @@ -930,6 +930,27 @@ async def _on_key(self, event: events.Key) -> None:
start, end = self.selection
self.replace(insert, start, end, maintain_selection_offset=False)

def _find_columns_to_next_tab_stop(self) -> int:
"""Get the location of the next tab stop after the cursors position on the current line.
If the cursor is already at a tab stop, this returns the *next* tab stop location.
Returns:
The number of cells to the next tab stop from the current cursor column.
"""
cursor_row, cursor_column = self.cursor_location
line_text = self.document[cursor_row]
indent_width = self.indent_width
if not line_text:
return indent_width

width_before_cursor = self.get_column_width(cursor_row, cursor_column)
spaces_to_insert = indent_width - (
(indent_width + width_before_cursor) % indent_width
)

return spaces_to_insert

def get_target_document_location(self, event: MouseEvent) -> Location:
"""Given a MouseEvent, return the row and column offset of the event in document-space.
Expand Down
29 changes: 29 additions & 0 deletions tests/text_area/test_edit_via_bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,35 @@ async def test_single_keypress_enter():
assert text_area.text == "\n" + TEXT


@pytest.mark.parametrize(
"content,cursor_column,cursor_destination",
[
("", 0, 4),
("x", 0, 4),
("x", 1, 4),
("xxx", 3, 4),
("xxxx", 4, 8),
("xxxxx", 5, 8),
("xxxxxx", 6, 8),
("💩", 1, 3),
("💩💩", 2, 6),
],
)
async def test_tab_with_spaces_goes_to_tab_stop(
content, cursor_column, cursor_destination
):
app = TextAreaApp()
async with app.run_test() as pilot:
text_area = app.query_one(TextArea)
text_area.indent_width = 4
text_area.load_text(content)
text_area.cursor_location = (0, cursor_column)

await pilot.press("tab")

assert text_area.cursor_location[1] == cursor_destination


async def test_delete_left():
app = TextAreaApp()
async with app.run_test() as pilot:
Expand Down

0 comments on commit 03196ca

Please sign in to comment.