diff --git a/src/textual/_text_backend.py b/src/textual/_text_backend.py new file mode 100644 index 000000000..cf2bd0978 --- /dev/null +++ b/src/textual/_text_backend.py @@ -0,0 +1,95 @@ +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass +class TextEditorBackend: + content: str = "" + cursor_index: int = 0 + + def set_content(self, text: str): + self.content = text + + def delete_back(self) -> bool: + """Delete the character behind the cursor + + Returns: True if the text content was modified. False otherwise. + """ + if self.cursor_index == 0: + return False + + new_text = ( + self.content[: self.cursor_index - 1] + self.content[self.cursor_index :] + ) + self.content = new_text + self.cursor_index = max(0, self.cursor_index - 1) + return True + + def delete_forward(self) -> bool: + """Delete the character in front of the cursor + + Returns: True if the text content was modified. False otherwise. + """ + if self.cursor_index == len(self.content): + return False + + new_text = ( + self.content[: self.cursor_index] + self.content[self.cursor_index + 1 :] + ) + self.content = new_text + return True + + def cursor_left(self) -> bool: + """Move the cursor 1 character left in the text""" + previous_index = self.cursor_index + new_index = max(0, previous_index - 1) + self.cursor_index = new_index + return previous_index != new_index + + def cursor_right(self) -> bool: + """Move the cursor 1 character right in the text""" + previous_index = self.cursor_index + new_index = min(len(self.content), previous_index + 1) + self.cursor_index = new_index + return previous_index != new_index + + def query_cursor_left(self) -> bool: + """Check if the cursor can move 1 character left in the text""" + previous_index = self.cursor_index + new_index = max(0, previous_index - 1) + return previous_index != new_index + + def query_cursor_right(self) -> bool: + """Check if the cursor can move right""" + previous_index = self.cursor_index + new_index = min(len(self.content), previous_index + 1) + return previous_index != new_index + + def cursor_text_start(self) -> bool: + if self.cursor_index == 0: + return False + + self.cursor_index = 0 + return True + + def cursor_text_end(self) -> bool: + text_length = len(self.content) + if self.cursor_index == text_length: + return False + + self.cursor_index = text_length + return True + + def insert_at_cursor(self, text: str) -> bool: + new_text = ( + self.content[: self.cursor_index] + text + self.content[self.cursor_index :] + ) + self.content = new_text + self.cursor_index = min(len(self.content), self.cursor_index + 1) + return True + + def get_range(self, start: int, end: int) -> str: + """Return the text between 2 indices. Useful for previews/views into + a subset of the content e.g. scrollable single-line input fields""" + return self.content[start:end] diff --git a/src/textual/widgets/text_input.py b/src/textual/widgets/text_input.py index c54b3df9a..7ff0d2572 100644 --- a/src/textual/widgets/text_input.py +++ b/src/textual/widgets/text_input.py @@ -1,13 +1,12 @@ from __future__ import annotations -from dataclasses import dataclass - from rich.console import RenderableType from rich.padding import Padding from rich.style import Style from rich.text import Text from textual import events +from textual._text_backend import TextEditorBackend from textual._types import MessageTarget from textual.app import ComposeResult from textual.geometry import Size @@ -16,98 +15,6 @@ from textual.message import Message from textual.widget import Widget -@dataclass -class TextEditorBackend: - content: str = "" - cursor_index: int = 0 - - def set_content(self, text: str): - self.content = text - - def delete_back(self) -> bool: - """Delete the character behind the cursor - - Returns: True if the text content was modified. False otherwise. - """ - if self.cursor_index == 0: - return False - - new_text = ( - self.content[: self.cursor_index - 1] + self.content[self.cursor_index :] - ) - self.content = new_text - self.cursor_index = max(0, self.cursor_index - 1) - return True - - def delete_forward(self) -> bool: - """Delete the character in front of the cursor - - Returns: True if the text content was modified. False otherwise. - """ - if self.cursor_index == len(self.content): - return False - - new_text = ( - self.content[: self.cursor_index] + self.content[self.cursor_index + 1 :] - ) - self.content = new_text - return True - - def cursor_left(self) -> bool: - """Move the cursor 1 character left in the text""" - previous_index = self.cursor_index - new_index = max(0, previous_index - 1) - self.cursor_index = new_index - return previous_index != new_index - - def cursor_right(self) -> bool: - """Move the cursor 1 character right in the text""" - previous_index = self.cursor_index - new_index = min(len(self.content), previous_index + 1) - self.cursor_index = new_index - return previous_index != new_index - - def query_cursor_left(self) -> bool: - """Check if the cursor can move 1 character left in the text""" - previous_index = self.cursor_index - new_index = max(0, previous_index - 1) - return previous_index != new_index - - def query_cursor_right(self) -> bool: - """Check if the cursor can move right""" - previous_index = self.cursor_index - new_index = min(len(self.content), previous_index + 1) - return previous_index != new_index - - def cursor_text_start(self) -> bool: - if self.cursor_index == 0: - return False - - self.cursor_index = 0 - return True - - def cursor_text_end(self) -> bool: - text_length = len(self.content) - if self.cursor_index == text_length: - return False - - self.cursor_index = text_length - return True - - def insert_at_cursor(self, text: str) -> bool: - new_text = ( - self.content[: self.cursor_index] + text + self.content[self.cursor_index :] - ) - self.content = new_text - self.cursor_index = min(len(self.content), self.cursor_index + 1) - return True - - def get_range(self, start: int, end: int) -> str: - """Return the text between 2 indices. Useful for previews/views into - a subset of the content e.g. scrollable single-line input fields""" - return self.content[start:end] - - class TextWidgetBase(Widget): STOP_PROPAGATE = set() @@ -184,7 +91,7 @@ class TextInput(TextWidgetBase, can_focus=True): } TextInput:hover { - background: $primary-darken-1; + background: $primary-darken-2; } TextInput:focus {