mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge branch 'main' of github.com:Textualize/textual into datatable-cell-keys
This commit is contained in:
@@ -378,20 +378,32 @@ class Input(Widget, can_focus=True):
|
|||||||
|
|
||||||
def action_cursor_left_word(self) -> None:
|
def action_cursor_left_word(self) -> None:
|
||||||
"""Move the cursor left to the start of a word."""
|
"""Move the cursor left to the start of a word."""
|
||||||
try:
|
if self.password:
|
||||||
*_, hit = re.finditer(self._WORD_START, self.value[: self.cursor_position])
|
# This is a password field so don't give any hints about word
|
||||||
except ValueError:
|
# boundaries, even during movement.
|
||||||
self.cursor_position = 0
|
self.action_home()
|
||||||
else:
|
else:
|
||||||
self.cursor_position = hit.start()
|
try:
|
||||||
|
*_, hit = re.finditer(
|
||||||
|
self._WORD_START, self.value[: self.cursor_position]
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
self.cursor_position = 0
|
||||||
|
else:
|
||||||
|
self.cursor_position = hit.start()
|
||||||
|
|
||||||
def action_cursor_right_word(self) -> None:
|
def action_cursor_right_word(self) -> None:
|
||||||
"""Move the cursor right to the start of a word."""
|
"""Move the cursor right to the start of a word."""
|
||||||
hit = re.search(self._WORD_START, self.value[self.cursor_position :])
|
if self.password:
|
||||||
if hit is None:
|
# This is a password field so don't give any hints about word
|
||||||
self.cursor_position = len(self.value)
|
# boundaries, even during movement.
|
||||||
|
self.action_end()
|
||||||
else:
|
else:
|
||||||
self.cursor_position += hit.start()
|
hit = re.search(self._WORD_START, self.value[self.cursor_position :])
|
||||||
|
if hit is None:
|
||||||
|
self.cursor_position = len(self.value)
|
||||||
|
else:
|
||||||
|
self.cursor_position += hit.start()
|
||||||
|
|
||||||
def action_delete_right(self) -> None:
|
def action_delete_right(self) -> None:
|
||||||
"""Delete one character at the current cursor position."""
|
"""Delete one character at the current cursor position."""
|
||||||
@@ -404,12 +416,19 @@ class Input(Widget, can_focus=True):
|
|||||||
|
|
||||||
def action_delete_right_word(self) -> None:
|
def action_delete_right_word(self) -> None:
|
||||||
"""Delete the current character and all rightward to the start of the next word."""
|
"""Delete the current character and all rightward to the start of the next word."""
|
||||||
after = self.value[self.cursor_position :]
|
if self.password:
|
||||||
hit = re.search(self._WORD_START, after)
|
# This is a password field so don't give any hints about word
|
||||||
if hit is None:
|
# boundaries, even during deletion.
|
||||||
self.value = self.value[: self.cursor_position]
|
self.action_delete_right_all()
|
||||||
else:
|
else:
|
||||||
self.value = f"{self.value[: self.cursor_position]}{after[hit.end()-1 :]}"
|
after = self.value[self.cursor_position :]
|
||||||
|
hit = re.search(self._WORD_START, after)
|
||||||
|
if hit is None:
|
||||||
|
self.value = self.value[: self.cursor_position]
|
||||||
|
else:
|
||||||
|
self.value = (
|
||||||
|
f"{self.value[: self.cursor_position]}{after[hit.end()-1 :]}"
|
||||||
|
)
|
||||||
|
|
||||||
def action_delete_right_all(self) -> None:
|
def action_delete_right_all(self) -> None:
|
||||||
"""Delete the current character and all characters to the right of the cursor position."""
|
"""Delete the current character and all characters to the right of the cursor position."""
|
||||||
@@ -437,14 +456,21 @@ class Input(Widget, can_focus=True):
|
|||||||
"""Delete leftward of the cursor position to the start of a word."""
|
"""Delete leftward of the cursor position to the start of a word."""
|
||||||
if self.cursor_position <= 0:
|
if self.cursor_position <= 0:
|
||||||
return
|
return
|
||||||
after = self.value[self.cursor_position :]
|
if self.password:
|
||||||
try:
|
# This is a password field so don't give any hints about word
|
||||||
*_, hit = re.finditer(self._WORD_START, self.value[: self.cursor_position])
|
# boundaries, even during deletion.
|
||||||
except ValueError:
|
self.action_delete_left_all()
|
||||||
self.cursor_position = 0
|
|
||||||
else:
|
else:
|
||||||
self.cursor_position = hit.start()
|
after = self.value[self.cursor_position :]
|
||||||
self.value = f"{self.value[: self.cursor_position]}{after}"
|
try:
|
||||||
|
*_, hit = re.finditer(
|
||||||
|
self._WORD_START, self.value[: self.cursor_position]
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
self.cursor_position = 0
|
||||||
|
else:
|
||||||
|
self.cursor_position = hit.start()
|
||||||
|
self.value = f"{self.value[: self.cursor_position]}{after}"
|
||||||
|
|
||||||
def action_delete_left_all(self) -> None:
|
def action_delete_left_all(self) -> None:
|
||||||
"""Delete all characters to the left of the cursor position."""
|
"""Delete all characters to the left of the cursor position."""
|
||||||
|
|||||||
@@ -66,6 +66,17 @@ async def test_delete_left_word_from_end() -> None:
|
|||||||
assert input.value == expected[input.id]
|
assert input.value == expected[input.id]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_password_delete_left_word_from_end() -> None:
|
||||||
|
"""Deleting word left from end of a password input should delete everything."""
|
||||||
|
async with InputTester().run_test() as pilot:
|
||||||
|
for input in pilot.app.query(Input):
|
||||||
|
input.action_end()
|
||||||
|
input.password = True
|
||||||
|
input.action_delete_left_word()
|
||||||
|
assert input.cursor_position == 0
|
||||||
|
assert input.value == ""
|
||||||
|
|
||||||
|
|
||||||
async def test_delete_left_all_from_home() -> None:
|
async def test_delete_left_all_from_home() -> None:
|
||||||
"""Deleting all left from home should do nothing."""
|
"""Deleting all left from home should do nothing."""
|
||||||
async with InputTester().run_test() as pilot:
|
async with InputTester().run_test() as pilot:
|
||||||
@@ -119,6 +130,16 @@ async def test_delete_right_word_from_home() -> None:
|
|||||||
assert input.value == expected[input.id]
|
assert input.value == expected[input.id]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_password_delete_right_word_from_home() -> None:
|
||||||
|
"""Deleting word right from home of a password input should delete everything."""
|
||||||
|
async with InputTester().run_test() as pilot:
|
||||||
|
for input in pilot.app.query(Input):
|
||||||
|
input.password = True
|
||||||
|
input.action_delete_right_word()
|
||||||
|
assert input.cursor_position == 0
|
||||||
|
assert input.value == ""
|
||||||
|
|
||||||
|
|
||||||
async def test_delete_right_word_from_end() -> None:
|
async def test_delete_right_word_from_end() -> None:
|
||||||
"""Deleting word right from end should not change the input's value."""
|
"""Deleting word right from end should not change the input's value."""
|
||||||
async with InputTester().run_test() as pilot:
|
async with InputTester().run_test() as pilot:
|
||||||
|
|||||||
@@ -97,6 +97,16 @@ async def test_input_left_word_from_end() -> None:
|
|||||||
assert input.cursor_position == expected_at[input.id]
|
assert input.cursor_position == expected_at[input.id]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_password_input_left_word_from_end() -> None:
|
||||||
|
"""Going left one word from the end in a password field should land at home."""
|
||||||
|
async with InputTester().run_test() as pilot:
|
||||||
|
for input in pilot.app.query(Input):
|
||||||
|
input.action_end()
|
||||||
|
input.password = True
|
||||||
|
input.action_cursor_left_word()
|
||||||
|
assert input.cursor_position == 0
|
||||||
|
|
||||||
|
|
||||||
async def test_input_right_word_from_home() -> None:
|
async def test_input_right_word_from_home() -> None:
|
||||||
"""Going right one word from the start should land correctly.."""
|
"""Going right one word from the start should land correctly.."""
|
||||||
async with InputTester().run_test() as pilot:
|
async with InputTester().run_test() as pilot:
|
||||||
@@ -112,6 +122,15 @@ async def test_input_right_word_from_home() -> None:
|
|||||||
assert input.cursor_position == expected_at[input.id]
|
assert input.cursor_position == expected_at[input.id]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_password_input_right_word_from_home() -> None:
|
||||||
|
"""Going right one word from the start of a password input should go to the end."""
|
||||||
|
async with InputTester().run_test() as pilot:
|
||||||
|
for input in pilot.app.query(Input):
|
||||||
|
input.password = True
|
||||||
|
input.action_cursor_right_word()
|
||||||
|
assert input.cursor_position == len(input.value)
|
||||||
|
|
||||||
|
|
||||||
async def test_input_right_word_from_end() -> None:
|
async def test_input_right_word_from_end() -> None:
|
||||||
"""Going right one word from the end should do nothing."""
|
"""Going right one word from the end should do nothing."""
|
||||||
async with InputTester().run_test() as pilot:
|
async with InputTester().run_test() as pilot:
|
||||||
|
|||||||
Reference in New Issue
Block a user