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