pytermgui.widgets.interactive.input_field

This module contains the InputField class.

  1"""This module contains the `InputField` class."""
  2
  3from __future__ import annotations
  4from typing import Any
  5
  6import string
  7
  8from ...ansi_interface import MouseAction, MouseEvent
  9from ...input import keys
 10from ...enums import HorizontalAlignment
 11from ...regex import real_length
 12from .. import styles as w_styles
 13from ..base import Label
 14
 15
 16class InputField(Label):
 17    """An element to display user input
 18
 19    This class does NOT read input. To use this widget, send it
 20    user data gathered by `pytermgui.input.getch` or other means.
 21
 22    Args:
 23        value: The default value of this InputField.
 24        prompt: Text to display to the left of the field.
 25        expect: Type object that all input should match. This type
 26              is called on each new key, and if a `ValueError` is
 27              raised the key is discarded. The `value` attribute
 28              is also converted using this type.
 29
 30    Example of usage:
 31
 32    ```python3
 33    import pytermgui as ptg
 34
 35    field = ptg.InputField()
 36
 37    root = ptg.Container(
 38        "[210 bold]This is an InputField!",
 39        field,
 40    )
 41
 42    while True:
 43        key = getch()
 44
 45        # Send key to field
 46        field.handle_key(key)
 47        root.print()
 48    ```
 49    """
 50
 51    styles = w_styles.StyleManager(
 52        value=w_styles.FOREGROUND,
 53        cursor=w_styles.MarkupFormatter("[inverse]{item}"),
 54        fill=w_styles.MarkupFormatter("[@243]{item}"),
 55    )
 56
 57    is_bindable = True
 58
 59    def __init__(
 60        self,
 61        value: str = "",
 62        prompt: str = "",
 63        expect: type | None = None,
 64        **attrs: Any,
 65    ) -> None:
 66        """Initialize object"""
 67
 68        super().__init__(prompt + value, **attrs)
 69
 70        self.parent_align = HorizontalAlignment.LEFT
 71
 72        self.value = value
 73        self.prompt = prompt
 74        self.cursor = real_length(self.value)
 75        self.expect = expect
 76
 77    @property
 78    def selectables_length(self) -> int:
 79        """Get length of selectables in object"""
 80
 81        return 1
 82
 83    @property
 84    def cursor(self) -> int:
 85        """Get cursor"""
 86
 87        return self._cursor
 88
 89    @cursor.setter
 90    def cursor(self, value: int) -> None:
 91        """Set cursor as an always-valid value"""
 92
 93        self._cursor = max(0, min(value, real_length(str(self.value))))
 94
 95    def handle_key(self, key: str) -> bool:
 96        """Handle keypress, return True if success, False if failure"""
 97
 98        def _run_callback() -> None:
 99            """Call callback if `keys.ANY_KEY` is bound"""
100
101            if keys.ANY_KEY in self._bindings:
102                method, _ = self._bindings[keys.ANY_KEY]
103                method(self, key)
104
105        if self.execute_binding(key):
106            return True
107
108        if key == keys.TAB:
109            return False
110
111        if key == keys.BACKSPACE and self.cursor > 0:
112            self.value = str(self.value)
113            left = self.value[: self.cursor - 1]
114            right = self.value[self.cursor :]
115            self.value = left + right
116
117            self.cursor -= 1
118
119            _run_callback()
120
121        elif key in [keys.LEFT, keys.CTRL_B]:
122            self.cursor -= 1
123
124        elif key in [keys.RIGHT, keys.CTRL_F]:
125            self.cursor += 1
126
127        # Ignore unhandled non-printing keys
128        elif key == keys.ENTER or key not in string.printable:
129            return False
130
131        # Add character
132        else:
133            if self.expect is not None:
134                try:
135                    self.expect(key)
136                except ValueError:
137                    return False
138
139            self.value = str(self.value)
140
141            left = self.value[: self.cursor] + key
142            right = self.value[self.cursor :]
143
144            self.value = left + right
145            self.cursor += len(key)
146            _run_callback()
147
148        if self.expect is not None and self.value != "":
149            self.value = self.expect(self.value)
150
151        return True
152
153    def handle_mouse(self, event: MouseEvent) -> bool:
154        """Handle mouse events"""
155
156        # Ignore mouse release events
157        if event.action is MouseAction.RELEASE:
158            return True
159
160        # Set cursor to mouse location
161        if event.action is MouseAction.LEFT_CLICK:
162            self.cursor = event.position[0] - self.pos[0]
163            return True
164
165        return super().handle_mouse(event)
166
167    def get_lines(self) -> list[str]:
168        """Get lines of object"""
169
170        # Cache value to be reset later
171        old = self.value
172
173        # Stringify value in case `expect` is set
174        self.value = str(self.value)
175
176        # Create sides separated by cursor
177        left = self.styles.fill(self.value[: self.cursor])
178        right = self.styles.fill(self.value[self.cursor + 1 :])
179
180        # Assign cursor character
181        if self.selected_index is None:
182            if len(self.value) <= self.cursor:
183                cursor_char = ""
184
185            else:
186                cursor_char = self.styles.fill(self.value[self.cursor])
187
188        # These errors are really weird, as there is nothing different with
189        # styles.cursor compared to others, which pass just fine.
190        elif len(self.value) > self.cursor:
191            cursor_char = self.styles.cursor(  # pylint: disable=not-callable
192                self.value[self.cursor]
193            )
194
195        else:
196            cursor_char = self.styles.cursor(" ")  # pylint: disable=not-callable
197
198        # Set new value, get lines using it
199        self.value = self.prompt
200
201        if len(self.prompt) > 0:
202            self.value += " "
203
204        self.value += left + cursor_char + right
205
206        lines = super().get_lines()
207
208        # Set old value
209        self.value = old
210
211        return [
212            line + self.styles.fill((self.width - real_length(line)) * " ")
213            for line in lines
214        ]
class InputField(pytermgui.widgets.base.Label):
 17class InputField(Label):
 18    """An element to display user input
 19
 20    This class does NOT read input. To use this widget, send it
 21    user data gathered by `pytermgui.input.getch` or other means.
 22
 23    Args:
 24        value: The default value of this InputField.
 25        prompt: Text to display to the left of the field.
 26        expect: Type object that all input should match. This type
 27              is called on each new key, and if a `ValueError` is
 28              raised the key is discarded. The `value` attribute
 29              is also converted using this type.
 30
 31    Example of usage:
 32
 33    ```python3
 34    import pytermgui as ptg
 35
 36    field = ptg.InputField()
 37
 38    root = ptg.Container(
 39        "[210 bold]This is an InputField!",
 40        field,
 41    )
 42
 43    while True:
 44        key = getch()
 45
 46        # Send key to field
 47        field.handle_key(key)
 48        root.print()
 49    ```
 50    """
 51
 52    styles = w_styles.StyleManager(
 53        value=w_styles.FOREGROUND,
 54        cursor=w_styles.MarkupFormatter("[inverse]{item}"),
 55        fill=w_styles.MarkupFormatter("[@243]{item}"),
 56    )
 57
 58    is_bindable = True
 59
 60    def __init__(
 61        self,
 62        value: str = "",
 63        prompt: str = "",
 64        expect: type | None = None,
 65        **attrs: Any,
 66    ) -> None:
 67        """Initialize object"""
 68
 69        super().__init__(prompt + value, **attrs)
 70
 71        self.parent_align = HorizontalAlignment.LEFT
 72
 73        self.value = value
 74        self.prompt = prompt
 75        self.cursor = real_length(self.value)
 76        self.expect = expect
 77
 78    @property
 79    def selectables_length(self) -> int:
 80        """Get length of selectables in object"""
 81
 82        return 1
 83
 84    @property
 85    def cursor(self) -> int:
 86        """Get cursor"""
 87
 88        return self._cursor
 89
 90    @cursor.setter
 91    def cursor(self, value: int) -> None:
 92        """Set cursor as an always-valid value"""
 93
 94        self._cursor = max(0, min(value, real_length(str(self.value))))
 95
 96    def handle_key(self, key: str) -> bool:
 97        """Handle keypress, return True if success, False if failure"""
 98
 99        def _run_callback() -> None:
100            """Call callback if `keys.ANY_KEY` is bound"""
101
102            if keys.ANY_KEY in self._bindings:
103                method, _ = self._bindings[keys.ANY_KEY]
104                method(self, key)
105
106        if self.execute_binding(key):
107            return True
108
109        if key == keys.TAB:
110            return False
111
112        if key == keys.BACKSPACE and self.cursor > 0:
113            self.value = str(self.value)
114            left = self.value[: self.cursor - 1]
115            right = self.value[self.cursor :]
116            self.value = left + right
117
118            self.cursor -= 1
119
120            _run_callback()
121
122        elif key in [keys.LEFT, keys.CTRL_B]:
123            self.cursor -= 1
124
125        elif key in [keys.RIGHT, keys.CTRL_F]:
126            self.cursor += 1
127
128        # Ignore unhandled non-printing keys
129        elif key == keys.ENTER or key not in string.printable:
130            return False
131
132        # Add character
133        else:
134            if self.expect is not None:
135                try:
136                    self.expect(key)
137                except ValueError:
138                    return False
139
140            self.value = str(self.value)
141
142            left = self.value[: self.cursor] + key
143            right = self.value[self.cursor :]
144
145            self.value = left + right
146            self.cursor += len(key)
147            _run_callback()
148
149        if self.expect is not None and self.value != "":
150            self.value = self.expect(self.value)
151
152        return True
153
154    def handle_mouse(self, event: MouseEvent) -> bool:
155        """Handle mouse events"""
156
157        # Ignore mouse release events
158        if event.action is MouseAction.RELEASE:
159            return True
160
161        # Set cursor to mouse location
162        if event.action is MouseAction.LEFT_CLICK:
163            self.cursor = event.position[0] - self.pos[0]
164            return True
165
166        return super().handle_mouse(event)
167
168    def get_lines(self) -> list[str]:
169        """Get lines of object"""
170
171        # Cache value to be reset later
172        old = self.value
173
174        # Stringify value in case `expect` is set
175        self.value = str(self.value)
176
177        # Create sides separated by cursor
178        left = self.styles.fill(self.value[: self.cursor])
179        right = self.styles.fill(self.value[self.cursor + 1 :])
180
181        # Assign cursor character
182        if self.selected_index is None:
183            if len(self.value) <= self.cursor:
184                cursor_char = ""
185
186            else:
187                cursor_char = self.styles.fill(self.value[self.cursor])
188
189        # These errors are really weird, as there is nothing different with
190        # styles.cursor compared to others, which pass just fine.
191        elif len(self.value) > self.cursor:
192            cursor_char = self.styles.cursor(  # pylint: disable=not-callable
193                self.value[self.cursor]
194            )
195
196        else:
197            cursor_char = self.styles.cursor(" ")  # pylint: disable=not-callable
198
199        # Set new value, get lines using it
200        self.value = self.prompt
201
202        if len(self.prompt) > 0:
203            self.value += " "
204
205        self.value += left + cursor_char + right
206
207        lines = super().get_lines()
208
209        # Set old value
210        self.value = old
211
212        return [
213            line + self.styles.fill((self.width - real_length(line)) * " ")
214            for line in lines
215        ]

An element to display user input

This class does NOT read input. To use this widget, send it user data gathered by pytermgui.input.getch or other means.

Args
  • value: The default value of this InputField.
  • prompt: Text to display to the left of the field.
  • expect: Type object that all input should match. This type is called on each new key, and if a ValueError is raised the key is discarded. The value attribute is also converted using this type.

Example of usage:

import pytermgui as ptg

field = ptg.InputField()

root = ptg.Container(
    "[210 bold]This is an InputField!",
    field,
)

while True:
    key = getch()

    # Send key to field
    field.handle_key(key)
    root.print()
InputField( value: str = '', prompt: str = '', expect: type | None = None, **attrs: Any)
60    def __init__(
61        self,
62        value: str = "",
63        prompt: str = "",
64        expect: type | None = None,
65        **attrs: Any,
66    ) -> None:
67        """Initialize object"""
68
69        super().__init__(prompt + value, **attrs)
70
71        self.parent_align = HorizontalAlignment.LEFT
72
73        self.value = value
74        self.prompt = prompt
75        self.cursor = real_length(self.value)
76        self.expect = expect

Initialize object

styles = {'value': StyleCall(obj=None, method=<function <lambda>>), 'cursor': StyleCall(obj=None, method=MarkupFormatter(markup='[inverse]{item}', ensure_strip=False, _markup_cache={})), 'fill': StyleCall(obj=None, method=MarkupFormatter(markup='[@243]{item}', ensure_strip=False, _markup_cache={}))}

Default styles for this class

is_bindable = True

Allow binding support

cursor: int

Get cursor

selectables_length: int

Get length of selectables in object

def handle_key(self, key: str) -> bool:
 96    def handle_key(self, key: str) -> bool:
 97        """Handle keypress, return True if success, False if failure"""
 98
 99        def _run_callback() -> None:
100            """Call callback if `keys.ANY_KEY` is bound"""
101
102            if keys.ANY_KEY in self._bindings:
103                method, _ = self._bindings[keys.ANY_KEY]
104                method(self, key)
105
106        if self.execute_binding(key):
107            return True
108
109        if key == keys.TAB:
110            return False
111
112        if key == keys.BACKSPACE and self.cursor > 0:
113            self.value = str(self.value)
114            left = self.value[: self.cursor - 1]
115            right = self.value[self.cursor :]
116            self.value = left + right
117
118            self.cursor -= 1
119
120            _run_callback()
121
122        elif key in [keys.LEFT, keys.CTRL_B]:
123            self.cursor -= 1
124
125        elif key in [keys.RIGHT, keys.CTRL_F]:
126            self.cursor += 1
127
128        # Ignore unhandled non-printing keys
129        elif key == keys.ENTER or key not in string.printable:
130            return False
131
132        # Add character
133        else:
134            if self.expect is not None:
135                try:
136                    self.expect(key)
137                except ValueError:
138                    return False
139
140            self.value = str(self.value)
141
142            left = self.value[: self.cursor] + key
143            right = self.value[self.cursor :]
144
145            self.value = left + right
146            self.cursor += len(key)
147            _run_callback()
148
149        if self.expect is not None and self.value != "":
150            self.value = self.expect(self.value)
151
152        return True

Handle keypress, return True if success, False if failure

def handle_mouse(self, event: pytermgui.ansi_interface.MouseEvent) -> bool:
154    def handle_mouse(self, event: MouseEvent) -> bool:
155        """Handle mouse events"""
156
157        # Ignore mouse release events
158        if event.action is MouseAction.RELEASE:
159            return True
160
161        # Set cursor to mouse location
162        if event.action is MouseAction.LEFT_CLICK:
163            self.cursor = event.position[0] - self.pos[0]
164            return True
165
166        return super().handle_mouse(event)

Handle mouse events

def get_lines(self) -> list[str]:
168    def get_lines(self) -> list[str]:
169        """Get lines of object"""
170
171        # Cache value to be reset later
172        old = self.value
173
174        # Stringify value in case `expect` is set
175        self.value = str(self.value)
176
177        # Create sides separated by cursor
178        left = self.styles.fill(self.value[: self.cursor])
179        right = self.styles.fill(self.value[self.cursor + 1 :])
180
181        # Assign cursor character
182        if self.selected_index is None:
183            if len(self.value) <= self.cursor:
184                cursor_char = ""
185
186            else:
187                cursor_char = self.styles.fill(self.value[self.cursor])
188
189        # These errors are really weird, as there is nothing different with
190        # styles.cursor compared to others, which pass just fine.
191        elif len(self.value) > self.cursor:
192            cursor_char = self.styles.cursor(  # pylint: disable=not-callable
193                self.value[self.cursor]
194            )
195
196        else:
197            cursor_char = self.styles.cursor(" ")  # pylint: disable=not-callable
198
199        # Set new value, get lines using it
200        self.value = self.prompt
201
202        if len(self.prompt) > 0:
203            self.value += " "
204
205        self.value += left + cursor_char + right
206
207        lines = super().get_lines()
208
209        # Set old value
210        self.value = old
211
212        return [
213            line + self.styles.fill((self.width - real_length(line)) * " ")
214            for line in lines
215        ]

Get lines of object